Compare commits

...

364 Commits

Author SHA1 Message Date
tibbi 8b27d9f379 updating changelog 2023-10-05 12:48:46 +02:00
tibbi 11ded3f6e9 update version to 5.19.3 2023-10-05 12:48:40 +02:00
Tibor Kaputa 7341cfdf20
Merge pull request #777 from naveensingh/fix_placeholder_color
Fix placeholder color and update commons
2023-10-05 12:40:42 +02:00
Naveen 3d6ab4c602
update commons 2023-10-05 15:46:25 +05:30
Naveen 5b3b58aedc
Set placeholder text color 2023-10-05 15:45:21 +05:30
Tibor Kaputa 96749ac330
Merge pull request #776 from naveensingh/fix_threading_crash
Execute `clearAllMessagesIfNeeded` callback on the main thread
2023-10-05 12:07:04 +02:00
Naveen 30ba6dce5b
Execute `clearAllMessagesIfNeeded` callback on the main thread 2023-10-05 14:37:19 +05:30
Tibor Kaputa fe0553650e
updating the f-droid icon 2023-10-05 09:17:45 +02:00
tibbi f5ce526ff6 updating changelog 2023-10-04 12:32:43 +02:00
tibbi d79c9a0ba9 update version to 5.19.2 2023-10-04 12:32:35 +02:00
tibbi 52a0a7fca6 updating commons 2023-10-04 12:13:29 +02:00
Tibor Kaputa 9987058f7d
Merge pull request #770 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-10-03 12:22:44 +02:00
Tibor Kaputa 9c1a348e74
Update strings.xml 2023-10-03 12:22:30 +02:00
Nguyễn Hoàng Minh b8b2313e88
Translated using Weblate (Vietnamese)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/vi/
2023-10-03 11:42:10 +02:00
Anonymous b913b475da
Translated using Weblate (Vietnamese)
Currently translated at 100.0% (0 of 0 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/vi/
2023-10-03 11:42:10 +02:00
Nguyễn Hoàng Minh 6f4909cd4a
Added translation using Weblate (Vietnamese) 2023-10-03 11:42:10 +02:00
Tibor Kaputa 1d804ebe24
Merge pull request #775 from esensar/fix/missing-archive-table
Prevent using `archive` table if it doesn't exist
2023-10-03 11:42:06 +02:00
Ensar Sarajčić 625b515064 Update setter variable name for `isArchiveAvailable` 2023-10-03 10:53:33 +02:00
Ensar Sarajčić a31af991c5 Rename constant and prefs key for `isArchiveAvailable` 2023-10-03 10:34:55 +02:00
Ensar Sarajčić e41630e543 Rename `useThreadsArchive` to `isArchiveAvailable` for clarity 2023-10-03 09:46:10 +02:00
Ensar Sarajčić 0e68da2710 Prevent using `archive` table if it doesn't exist
This closes #773
2023-10-03 09:38:45 +02:00
tibbi 30b54076ce updating commons 2023-09-30 21:08:50 +02:00
Tibor Kaputa e735502700
Merge pull request #763 from naveensingh/fix_r8_crash
Properly clear messages from database
2023-09-30 20:35:21 +02:00
Naveen 4582bc5289
Properly clear messages from database 2023-09-30 02:21:49 +05:30
Tibor Kaputa 67e9b4587b
Merge pull request #757 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-09-28 23:16:30 +02:00
Lionel HANNEQUIN 6fe03875fe
Translated using Weblate (French)
Currently translated at 100.0% (19 of 19 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/fr/
2023-09-27 18:07:35 +02:00
gallegonovato d707589076
Translated using Weblate (Spanish)
Currently translated at 100.0% (19 of 19 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/es/
2023-09-27 18:07:35 +02:00
Lionel HANNEQUIN 77ab71885a
Translated using Weblate (French)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-09-27 18:07:35 +02:00
Puppelimies 2059537d44
Translated using Weblate (Swedish)
Currently translated at 100.0% (19 of 19 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/sv/
2023-09-25 07:01:32 +02:00
Tibor Kaputa 67ae69caf1
Merge pull request #754 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-09-23 19:19:17 +02:00
Puppelimies 4463d6b697
Translated using Weblate (Finnish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fi/
2023-09-23 17:53:05 +02:00
gallegonovato 390a9834e0
Translated using Weblate (Spanish)
Currently translated at 100.0% (19 of 19 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/es/
2023-09-22 02:03:01 +02:00
gallegonovato a0b7a93413
Translated using Weblate (Spanish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/es/
2023-09-22 02:03:01 +02:00
Peter Dave Hello 580ae99eef
Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hant/
2023-09-22 00:19:35 +02:00
gallegonovato 7a4fcd8d1a
Translated using Weblate (Spanish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/es/
2023-09-21 11:22:14 +02:00
tibbi 6ecc87c433 updating changelog 2023-09-20 09:18:05 +02:00
tibbi a5384af91f update version to 5.19.1 2023-09-20 09:17:59 +02:00
tibbi e2a8b69a8d updating commons 2023-09-20 09:11:41 +02:00
Tibor Kaputa c1841a8a11
Merge pull request #752 from naveensingh/fix_751_crash
Reset database on next app update
2023-09-20 09:06:58 +02:00
Naveen 50198b1898
Add proguard rules for `Attachment` 2023-09-20 00:36:14 +05:30
Naveen 3ffc6874cb
Add proguard rules for `SimpleContact` 2023-09-19 23:52:46 +05:30
Naveen b63d904dfe
Remove unused kotlin-parcelize plugin 2023-09-19 23:43:41 +05:30
Naveen a87f33f88f
Reset database on app update (again) 2023-09-19 23:41:54 +05:30
tibbi e255d7284b updating commons 2023-09-19 12:06:34 +02:00
tibbi d5bdb11011 updating changelog 2023-09-19 09:16:36 +02:00
tibbi a2a68113f5 update version to 5.19.0 2023-09-19 09:16:27 +02:00
tibbi 2719605ce8 updating commons 2023-09-19 09:09:45 +02:00
tibbi 66bf43f653 lets actually require the Fingerprint permission for app locking 2023-09-18 21:54:32 +02:00
tibbi a62c88a96b fixing a setting label 2023-09-18 21:45:10 +02:00
tibbi aa478f0463 do not require the fingerprint permission 2023-09-18 21:45:04 +02:00
Tibor Kaputa dc7542d8c1
Merge pull request #745 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-09-18 21:33:56 +02:00
elgratea f645f06403
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/bg/
2023-09-18 19:33:38 +00:00
J. Lavoie 74710ca692
Translated using Weblate (Italian)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/it/
2023-09-18 19:33:38 +00:00
Lionel HANNEQUIN 8bbfa720da
Translated using Weblate (French)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-09-18 19:33:37 +00:00
Tibor Kaputa 0bf9938328
Merge pull request #748 from fatihergin/fix/contact-names-missing-on-blocked-numbers
Fix/contact names missing on blocked numbers
2023-09-18 21:33:32 +02:00
tibbi a2d60b2a2b updating commons 2023-09-18 21:29:42 +02:00
fatih ergin c65755ee24 update commons for accessing contact names correctly 2023-09-18 22:23:17 +03:00
fatih ergin 912fff1f43 add READ_SYNC_SETTINGS permission to access contact sources correctly 2023-09-18 22:21:28 +03:00
tibbi b6388bfe9c updating commons 2023-09-18 13:41:35 +02:00
Tibor Kaputa 6ba24c1b29
Merge pull request #747 from esensar/gradle-deprecation-warnings
Clean up gradle deprecation warnings
2023-09-13 15:15:33 +02:00
Ensar Sarajčić 2d14709169 Clean up gradle deprecation warnings 2023-09-13 15:07:46 +02:00
tibbi e555cf699d updating commons and gradle 2023-09-11 23:26:55 +02:00
tibbi 1d370269c1 updating commons 2023-09-11 13:01:19 +02:00
Tibor Kaputa 3b9d5c4e50
Merge pull request #741 from Usland123/patch-1
Update strings.xml
2023-09-11 12:56:00 +02:00
Tibor Kaputa a58b839a0d
Merge pull request #742 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-09-11 12:55:48 +02:00
Sergio Marques 8a0fa650bd
Translated using Weblate (Portuguese)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pt/
2023-09-11 12:51:23 +02:00
en2sv e5e00b0462
Translated using Weblate (Swedish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-09-09 21:14:25 +02:00
Priit Jõerüüt 76bba30faf
Translated using Weblate (Estonian)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/et/
2023-09-07 06:29:05 +02:00
Komjaunietis Latvijas 78b9407820
Translated using Weblate (Latvian)
Currently translated at 97.1% (104 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/lv/
2023-09-03 19:00:15 +02:00
Usland 72923362ee
Update strings.xml 2023-09-02 21:36:55 +03:00
Tibor Kaputa 582115fbe8
Merge pull request #738 from naveensingh/update_smsmms_lib
Update android-smsmms library
2023-08-30 11:38:44 +02:00
Naveen dd563774ee
Update android-smsmms library 2023-08-30 15:04:54 +05:30
Tibor Kaputa 2b876f7a6f
Merge pull request #737 from spkprs/patch-19
Update.xml
2023-08-30 08:53:42 +02:00
spkprs 1d7c73ede8
Update.xml 2023-08-30 07:42:13 +03:00
Tibor Kaputa 73fbbf3289
Merge pull request #736 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-08-29 23:49:59 +02:00
Josep M. Ferrer 450fd1c4a7
Translated using Weblate (Catalan)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-08-29 23:44:22 +02:00
Tibor Kaputa 6f84ad0bfe
Merge pull request #733 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-08-29 23:06:41 +02:00
Oğuz Ersen 1616cae2af
Translated using Weblate (Turkish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-08-28 18:50:11 +02:00
Rex_sa 37add6e176
Translated using Weblate (Arabic)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-08-27 12:04:35 +02:00
Eric 0cc60f9649
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-08-27 12:04:35 +02:00
solokot a0e61dc844
Translated using Weblate (Russian)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-08-27 12:04:35 +02:00
Agnieszka C 1ce1eed123
Translated using Weblate (Polish)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-08-27 12:04:34 +02:00
Guillaume 8a106c3474
Translated using Weblate (Dutch)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-08-27 12:04:34 +02:00
Guillaume 7d48a02313
Translated using Weblate (French)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-08-27 12:04:34 +02:00
VfBFan 954e66e83e
Translated using Weblate (German)
Currently translated at 100.0% (107 of 107 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-08-27 12:04:33 +02:00
Tibor Kaputa a14ba9430a
Merge pull request #732 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-08-24 23:06:05 +02:00
elgratea 05e226e9c0
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/bg/
2023-08-24 23:01:17 +02:00
Tibor Kaputa 43ee2a99ba
Merge pull request #730 from naveensingh/show_mms_download_failure
Show error toast when mms download fails
2023-08-24 23:01:12 +02:00
Naveen db3493c146
Merge remote-tracking branch 'origin/show_mms_download_failure' into show_mms_download_failure 2023-08-24 23:46:12 +05:30
Naveen 9ea707d2ba
Show long error toasts 2023-08-24 23:45:35 +05:30
Tibor Kaputa bb7869b5a8
Merge pull request #727 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-08-24 20:08:26 +02:00
Tibor Kaputa 0c976f8f3f
updating the slovak translation 2023-08-24 20:04:43 +02:00
J. Lavoie fdeff6daa1
Translated using Weblate (Italian)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/it/
2023-08-24 20:03:48 +02:00
J. Lavoie d5dc1dba88
Translated using Weblate (French)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-08-24 20:03:48 +02:00
Slávek Banko 2dc672ed56
Translated using Weblate (Czech)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/cs/
2023-08-24 20:03:48 +02:00
Tibor Kaputa aa8d1628ae
Merge pull request #731 from naveensingh/non_transitive_r_class
Migrate to non-transitive R Classes
2023-08-24 20:03:43 +02:00
Naveen 69ff590cf0
Migrate to non-transitive R Classes 2023-08-24 20:59:34 +05:30
Naveen e76130d037
Show error toast when mms download fails 2023-08-23 15:41:45 +05:30
Tibor Kaputa 166998f86e
Merge pull request #729 from naveensingh/fix_adapter_crash
Avoid modifying items while list is being updated
2023-08-22 16:43:50 +02:00
Naveen 1089d838db
Avoid modifying items while list is being updated
`performFiltering` is always called on a worker thread. UI should only be updated in `publishResults`.
2023-08-22 18:04:08 +05:30
Tibor Kaputa 7135ca44bf
Merge pull request #726 from naveensingh/sdk_and_viewbinding_migration
Migrate to View binding, SDK 34 and Version catalogs
2023-08-22 12:30:02 +02:00
Naveen c3fe4d8aca
Add a new line 2023-08-22 15:33:48 +05:30
Naveen cdcc9e2140
Remove unnecessary comma 2023-08-22 15:12:08 +05:30
Naveen 66be224b38
Remove nested apply usage 2023-08-22 15:11:05 +05:30
Naveen 8bc6659d71
Set drawable size in code 2023-08-22 14:27:40 +05:30
Naveen fead93ddf7
Remove `messageHolderBinding` variable 2023-08-22 14:09:04 +05:30
Naveen 8ebcaa3016
Add some new lines 2023-08-22 14:01:17 +05:30
Naveen 71e0c177e1
Update commons 2023-08-18 19:15:48 +05:30
Naveen dccbc6ce7a
Use View binding in AutoCompleteTextViewAdapter.kt 2023-08-18 17:18:30 +05:30
Naveen c4b1e5b6b3
Restore proper app version info! 2023-08-17 19:23:08 +05:30
Naveen dae74d5ded
Merge remote-tracking branch 'origin/sdk_and_viewbinding_migration' into sdk_and_viewbinding_migration 2023-08-17 16:46:48 +05:30
Naveen c7a9f44663
Minor code improvement 2023-08-17 16:45:03 +05:30
Naveen Singh e27e33b323
Merge branch 'SimpleMobileTools:master' into sdk_and_viewbinding_migration 2023-08-17 16:35:33 +05:30
Naveen 0c01e607bb
Migrate from kotlin synthetics to View binding 2023-08-17 15:54:29 +05:30
Naveen 3e1675d579
Migrate build scripts to use version catalogs and kts 2023-08-17 12:57:42 +05:30
Tibor Kaputa b2696c56a3
Merge pull request #716 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-08-14 13:51:59 +02:00
Tibor Kaputa 7d57b9a9a9
updating a slovak translation 2023-08-14 13:51:31 +02:00
Naveen 496ff77532
Bump jvm heap size to 8192m 2023-08-14 17:17:07 +05:30
en2sv ac266db6c0
Translated using Weblate (Swedish)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-08-13 21:51:27 +02:00
Milan Šalka 494624b0c3
Translated using Weblate (Slovak)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sk/
2023-08-11 12:52:12 +02:00
Agnieszka C cca6f90cfa
Translated using Weblate (Polish)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-08-02 08:07:13 +02:00
Gabriel Camargo ab369f515d
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pt_BR/
2023-07-31 15:08:50 +02:00
cwpute d972a494d2
Translated using Weblate (French)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-07-31 15:08:49 +02:00
Tibor Kaputa 8a933a2e44
Merge pull request #714 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-28 08:28:26 +02:00
solokot dcb9bbcc4d
Translated using Weblate (Russian)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-07-28 01:07:46 +02:00
Tibor Kaputa 823c6883dc
Merge pull request #712 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-26 17:43:11 +02:00
Josep M. Ferrer e77c6b5da4
Translated using Weblate (Catalan)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-07-26 17:20:34 +02:00
Priit Jõerüüt 6f937ef167
Translated using Weblate (Estonian)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/et/
2023-07-26 17:20:33 +02:00
Rex_sa d65c7f0689
Translated using Weblate (Arabic)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-07-26 17:20:33 +02:00
Eric 98cb5518a1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-07-26 17:20:33 +02:00
Oğuz Ersen 97fcab0123
Translated using Weblate (Turkish)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-07-26 17:20:33 +02:00
Agnieszka C 5a1aec1cdd
Translated using Weblate (Polish)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-07-26 17:20:33 +02:00
Guillaume 6b11bbd408
Translated using Weblate (Dutch)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-07-26 17:20:33 +02:00
VfBFan 3c88dcf8f6
Translated using Weblate (German)
Currently translated at 100.0% (106 of 106 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-07-26 17:20:33 +02:00
Tibor Kaputa f17f32cd62
Merge pull request #709 from spkprs/patch-18
Update strings.xml
2023-07-26 17:20:26 +02:00
spkprs c1575ef821
Update strings.xml 2023-07-25 17:18:12 +03:00
Tibor Kaputa 23c4229eb0
Merge pull request #708 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-25 16:01:10 +02:00
Tibor Kaputa a16f37b12a
Merge pull request #700 from esensar/feature/451-recycle-bin
Add support for recycle bin for messages
2023-07-25 16:00:47 +02:00
Tibor Kaputa c3ed0dfddf
updating the slovak strings 2023-07-25 16:00:02 +02:00
en2sv e1f999f26b
Translated using Weblate (Swedish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-07-25 15:57:36 +02:00
Ensar Sarajčić b85661eb4d Always calculate snippet when reading conversations
This ensures that correct snippet is displayed, because of moving
messages to recycle bin. This must be done for conversations in
recycle bin as well as regular conversations, because both can be
affected, depending on which messages are moved to recycle bin.
2023-07-25 15:38:24 +02:00
Ensar Sarajčić 24756285cc Use recycle bin string for deletion when recycle bin is active 2023-07-25 14:54:32 +02:00
Ensar Sarajčić 3cb3e24e2c Add Serbian recycle-bin strings 2023-07-25 08:16:48 +02:00
Ensar Sarajčić 0ec0b89cea Add Croatian strings for recycle-bin 2023-07-25 08:13:34 +02:00
Ensar Sarajčić 6ca6462155 Use capital R in recycle bin for consistency 2023-07-25 08:08:38 +02:00
Ensar Sarajčić 450a0c22d1 Merge branch 'master' into feature/451-recycle-bin 2023-07-25 08:08:04 +02:00
Tibor Kaputa e580279890
Merge pull request #706 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-24 23:33:29 +02:00
en2sv fb1b784a80
Translated using Weblate (Swedish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-07-24 23:32:31 +02:00
Sergio Marques 461b1efbde
Translated using Weblate (Portuguese)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pt/
2023-07-24 23:32:31 +02:00
Josep M. Ferrer 94ef53eba2
Translated using Weblate (Catalan)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-07-24 23:32:31 +02:00
solokot 8d2b93793a
Translated using Weblate (Russian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-07-24 23:32:31 +02:00
Tibor Kaputa 96c8b4b1ec
Merge pull request #707 from esensar/fix/554-duplicate-resent-messages
Prevent duplication of messages on resend
2023-07-24 23:32:25 +02:00
Ensar Sarajčić 0a19596053 Update string for show recycle bin menu option 2023-07-24 14:49:01 +02:00
Ensar Sarajčić dbf582b239 Merge branch 'master' into feature/451-recycle-bin 2023-07-24 14:47:50 +02:00
Ensar Sarajčić 312f5bd0a8 Prevent duplication of messages on resend
This prevents duplication by ensuring that message is just
updated in case of SMS, instead of creating a new entry. In case
of MMS, due to the way it is sent internally, we delete original
message once we get result of the new one.

This closes #554
2023-07-24 11:03:48 +02:00
Tibor Kaputa de695a5a62
Merge pull request #699 from Merkost/export_import_settings
Export import settings section
2023-07-24 10:55:26 +02:00
Tibor Kaputa deecd78650
minor code style update 2023-07-24 10:55:10 +02:00
merkost 014ac2a4cf Added invalid_file_format toast on XML importing 2023-07-24 13:40:38 +10:00
Tibor Kaputa f875688bf3
Merge pull request #697 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-22 23:20:05 +02:00
solokot dc09352213
Translated using Weblate (Russian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-07-22 23:19:49 +02:00
Priit Jõerüüt d0fc054774
Translated using Weblate (Estonian)
Currently translated at 100.0% (17 of 17 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/et/
2023-07-22 23:19:49 +02:00
VfBFan 159e73ecc9
Translated using Weblate (German)
Currently translated at 100.0% (17 of 17 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/de/
2023-07-22 23:19:49 +02:00
Priit Jõerüüt 264745c216
Translated using Weblate (Estonian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/et/
2023-07-22 23:19:49 +02:00
Rex_sa 543050b9d4
Translated using Weblate (Arabic)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-07-22 23:19:49 +02:00
Eric e09de4d334
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-07-22 23:19:49 +02:00
Oğuz Ersen b92d85a0b6
Translated using Weblate (Turkish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-07-22 23:19:49 +02:00
Agnieszka C 661293be41
Translated using Weblate (Polish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-07-22 23:19:49 +02:00
Guillaume 690d4aca30
Translated using Weblate (Dutch)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-07-22 23:19:49 +02:00
VfBFan dac1bced86
Translated using Weblate (German)
Currently translated at 100.0% (100 of 100 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-07-22 23:19:49 +02:00
abc0922001 59b927333b
Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (17 of 17 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger metadata
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger-metadata/zh_Hant/
2023-07-22 23:19:49 +02:00
VfBFan 68d88e97d0
Translated using Weblate (German)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-07-22 23:19:49 +02:00
VfBFan b135263fa6
Translated using Weblate (German)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-07-22 23:19:49 +02:00
Tibor Kaputa edc71fb861
Merge pull request #705 from spkprs/patch-17
Update strings.xml
2023-07-22 23:19:43 +02:00
merkost e244fd5a53 Moved "Importing" toast 2023-07-22 22:54:05 +10:00
spkprs 3d95ce2b83
Update strings.xml 2023-07-22 12:51:21 +03:00
merkost 39ca2d6079 Added proguard-rules.pro for Kotlin Serialization 2023-07-22 10:05:45 +10:00
merkost 8c0508b0c1 Refactored message restoring in MessagesImporter 2023-07-22 00:31:24 +10:00
merkost 842368d0f4 Split lines in MessagesReader 2023-07-22 00:14:55 +10:00
merkost c72dc199aa ExportMessagesDialog code refactoring 2023-07-22 00:10:17 +10:00
Ensar Sarajčić bcb42d0ff5 Ensure recycle bin is ignored when disabled 2023-07-20 16:34:05 +02:00
Ensar Sarajčić cc6e9358f6 Ensure recycled messages don't reappear in threads 2023-07-20 16:26:00 +02:00
Ensar Sarajčić 565f991932 Update recycle bin related strings 2023-07-20 16:20:29 +02:00
Ensar Sarajčić 3f06b521bf Add separate screen for recycle bin messages 2023-07-20 16:04:51 +02:00
Ensar Sarajčić 31be5d3d95 Remove empty recycle bin menu item if there are no recycle bin conversations 2023-07-20 14:18:24 +02:00
Ensar Sarajčić 372dbaeaa4 Remove unusude useArchive property 2023-07-20 14:12:38 +02:00
Ensar Sarajčić b29d664dc4 Merge branch 'master' into feature/451-recycle-bin 2023-07-20 14:12:20 +02:00
merkost ec6bf55025 Added and refactored xml import support 2023-07-20 13:37:54 +10:00
merkost 05ced83909 Merge branch 'master' into export_import_settings
# Conflicts:
#	app/src/main/res/menu/menu_main.xml
2023-07-20 12:52:42 +10:00
tibbi fd65d26f8f updating commons and room 2023-07-19 16:52:20 +02:00
Tibor Kaputa 0c5242df2d
Merge pull request #698 from esensar/feature/177-conversations-archive
Implement archive feature for conversations using system API
2023-07-19 16:40:16 +02:00
Ensar Sarajčić e86e089dc5 Move thread handling to unarchiveConversation method 2023-07-19 15:31:02 +02:00
Tibor Kaputa b9f956f7e8
updating the slovak translations 2023-07-19 14:56:40 +02:00
Tibor Kaputa 9208eedf6b
adding an empty line 2023-07-19 14:52:51 +02:00
Ensar Sarajčić a07ac2c8e6 Add Serbian translation for archive_confirmation 2023-07-19 10:14:25 +02:00
Ensar Sarajčić a3d723835c Add Croatian translation of archive_confirmation 2023-07-19 10:13:29 +02:00
Ensar Sarajčić e07fbe40a6 Add confirmation for archiving conversations 2023-07-19 10:11:52 +02:00
Ensar Sarajčić 5dff8367e3 Update label for archive emptying for clarity 2023-07-19 10:08:18 +02:00
Ensar Sarajčić b9b85ea6a7 Remove options menu on ArchivedConversationsActivity when there are no conversations 2023-07-19 10:07:33 +02:00
Tibor Kaputa 4501e2fe6b
Merge pull request #693 from wilsonrc/feature/AddExactAlarmPermissionDialog
add asking for Exact alarm permission before scheduling
2023-07-19 07:28:59 +02:00
Wilson f6b5bbf455 add asking for Exact alarm permission before scheduling a message above Android API S 2023-07-18 16:21:18 -04:00
Ensar Sarajčić b0141fe93d Add recycle bin list similar to main conversations list 2023-07-18 15:19:30 +02:00
Ensar Sarajčić d560720ac3 Fix index creation in the migration 2023-07-18 11:45:47 +02:00
Ensar Sarajčić 555b6ebea3 Add support for recycle bin for messages
This adds support for moving messages to recycle bin instead of
deleting them right away. The feature is not active by default.

This closes #451
2023-07-18 11:34:25 +02:00
merkost db5decfcd8 Added additional types for txt or xml and moved import logic to Importer 2023-07-18 17:17:52 +10:00
Konstantin Merenkov 321e4f11ff
Merge branch 'master' into export_import_settings 2023-07-18 16:27:09 +10:00
merkost 47866a1c19 Settings activity configured import/export 2023-07-18 16:24:04 +10:00
merkost 679236e3fa MessagesImporter and MessagesReader refactoring 2023-07-18 16:23:46 +10:00
merkost 4d378e819c ImportResult extracted to a separate class and MainActivity cleared 2023-07-18 16:23:04 +10:00
merkost 30b100b62f Dialogs refactoring 2023-07-18 16:22:14 +10:00
merkost 5a8cc0f14d Added BackupType with BackupSerializer 2023-07-18 16:21:44 +10:00
Ensar Sarajčić 1b8cfee9ea Remove unused strings 2023-07-18 08:21:22 +02:00
merkost 5363af1071 Added serialization 2023-07-18 12:38:31 +10:00
merkost 56ce7c5aa4 Added settings migrating section 2023-07-18 12:32:06 +10:00
Ensar Sarajčić 857a4f0b93 Implement archive functionality using system API 2023-07-17 16:43:31 +02:00
Tibor Kaputa 53aa4495a0
Merge pull request #695 from esensar/fix/apn-utils-crash
Remove ApnUtils usage in the app
2023-07-16 18:13:22 +02:00
Ensar Sarajčić 222b96e8c5 Remove ApnUtils usage in the app
Using ApnUtils causes crash on newer Android versions (https://github.com/SimpleMobileTools/Simple-SMS-Messenger/pull/683#issuecomment-1637036718)
This reverts this part of changes from https://github.com/SimpleMobileTools/Simple-SMS-Messenger/pull/687, since other
changes have fixed the issue. This part is not as important (required to send reception ACK to MMSC, which is apparently not needed for all carriers)
2023-07-16 16:25:29 +02:00
Tibor Kaputa ea4d67c1df
Merge pull request #694 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-16 13:18:55 +02:00
solokot 5a0e9d26fc
Translated using Weblate (Russian)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-07-16 13:07:48 +02:00
Tibor Kaputa 1995da2916
Merge pull request #685 from esensar/feature/519-xml-import
Add support for importing XML exports
2023-07-15 18:41:26 +02:00
Ensar Sarajčić c79242f571 Rename JSON_FILE_EXT to JSON_FILE_EXTENSION 2023-07-15 16:33:21 +02:00
Ensar Sarajčić a35edce84a Add support for .txt files for importing 2023-07-15 16:32:33 +02:00
Ensar Sarajčić a6b97698bf Update mime type and file extension constants for clarity 2023-07-15 16:24:32 +02:00
Ensar Sarajčić 0ad178fddb Rename JSON import constants to better reflect contents 2023-07-15 13:59:58 +02:00
Tibor Kaputa a90e555186
Merge pull request #691 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-14 14:14:01 +02:00
elgratea 43c4c1395c
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/bg/
2023-07-14 14:12:04 +02:00
Josep M. Ferrer ec65179a77
Translated using Weblate (Catalan)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-07-14 14:12:04 +02:00
Rex_sa bce96691a3
Translated using Weblate (Arabic)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-07-14 14:12:04 +02:00
Eric e9ff8bf323
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-07-14 14:12:04 +02:00
Oğuz Ersen ba88dc8dce
Translated using Weblate (Turkish)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-07-14 14:12:04 +02:00
Guillaume 2c2c629a5c
Translated using Weblate (Dutch)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-07-14 14:12:03 +02:00
Tibor Kaputa c5cf200fd0
Merge pull request #690 from spkprs/patch-16
Update.xml
2023-07-14 14:11:57 +02:00
spkprs 2a5eff964c
Update strings.xml 2023-07-14 14:28:18 +03:00
tibbi 17f1bf62b1 removing a redundant type definition 2023-07-14 10:00:06 +02:00
Tibor Kaputa eb35bf3761
Merge pull request #687 from esensar/fix/duplicate-mms
Ensure that MMS reception ACK is sent to prevent duplicate MMS
2023-07-14 09:59:40 +02:00
Ensar Sarajčić ca66206034 Update android-smsmms ref to use tibbi fork 2023-07-14 09:39:32 +02:00
Ensar Sarajčić 4d13fa1079 Disable archive by default 2023-07-13 16:38:09 +02:00
Ensar Sarajčić c1446194a1 Change archived conversations section label to archive 2023-07-13 16:37:32 +02:00
Ensar Sarajčić 3fc1ddbff9 Update android-smsmms to include https://github.com/tibbi/android-smsmms/pull/14 2023-07-13 16:05:41 +02:00
Tibor Kaputa 8ebef8801d
Merge pull request #686 from esensar/fix/locked-export
Fix export for LOCKED property for SMS
2023-07-13 15:44:32 +02:00
Ensar Sarajčić 1b7874446b Add index to db migration for archived_conversations 2023-07-13 13:52:54 +02:00
Ensar Sarajčić c51dc0b935 Fix archived_conversations creation migration 2023-07-13 10:33:02 +02:00
Tibor Kaputa 912776035d
Merge pull request #688 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-07-13 10:12:52 +02:00
Agnieszka C 512a354f44
Translated using Weblate (Polish)
Currently translated at 100.0% (91 of 91 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-07-13 10:12:24 +02:00
Josep M. Ferrer 955374b470
Translated using Weblate (Catalan)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-07-13 04:28:43 +02:00
Rex_sa 8ec167da4e
Translated using Weblate (Arabic)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-07-13 04:28:43 +02:00
Eric 2d6f05f0f5
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-07-13 04:28:43 +02:00
Oğuz Ersen 705181e076
Translated using Weblate (Turkish)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-07-13 04:28:43 +02:00
solokot 0c4774cd56
Translated using Weblate (Russian)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-07-13 04:28:43 +02:00
Agnieszka C 52fecff926
Translated using Weblate (Polish)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-07-13 04:28:43 +02:00
Guillaume d15384db26
Translated using Weblate (Dutch)
Currently translated at 100.0% (86 of 86 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-07-13 04:28:43 +02:00
Ensar Sarajčić ab898bfcbe Update android-smsmms for cleaner MmsReceiver 2023-07-12 18:05:15 +02:00
Ensar Sarajčić d97a6f6a5f Ensure that MMS reception ACK is sent to prevent duplicate MMS
This implements the getMmscInfoForReceptionAck method
in MmsReceiver which is according to android-smsmms
library required for some carriers, since otherwise
MMS would be duplicated. Unfortunately,
accessing MMSC information is more restricted with
each version of Android and this implementation relies
on system database on older versions and uses APN database
included with android-smsmms for newer versions, which will
work only on the default SIM card.
2023-07-12 17:45:24 +02:00
Ensar Sarajčić 4b3fa422be Move Show archived conversations to strings.xml 2023-07-12 12:32:28 +02:00
Ensar Sarajčić 0e2dd357d1 Fix export for LOCKED property for SMS 2023-07-12 12:15:30 +02:00
Ensar Sarajčić e825e44f54 Add support for importing XML exports
This covers importing from Signal and Silence applications export

This closes #519
2023-07-12 12:13:59 +02:00
Ensar Sarajčić 7dbd6c5d9f Add proper strings for archive operations 2023-07-12 10:37:20 +02:00
Ensar Sarajčić 47861f605d Add support for archiving conversations
Archiving messages currently acts like recycle bin in
Simple Gallery, meaning that after 30 days, conversations
will be deleted permanently. Any updates to the conversation
(new messages) removes it from archive.

This closes #177
2023-07-11 16:52:47 +02:00
Tibor Kaputa 7ca11c8427
Merge pull request #682 from esensar/feature/19-message-details
Add message details menu button
2023-07-11 15:25:10 +02:00
Tibor Kaputa aecaff6d6a
updating the slovak translation 2023-07-11 15:24:32 +02:00
Ensar Sarajčić cf7003e3b4 Remove extra spaces in strings files 2023-07-11 15:11:53 +02:00
Ensar Sarajčić 98eb62e1b6 Move message_details_sim to donottranslate 2023-07-11 13:45:15 +02:00
Ensar Sarajčić d27a2f5747 Update Simple-Commons ref 2023-07-11 13:44:50 +02:00
Ensar Sarajčić 3adfdd401e Update dialog to reuse properties dialog from file manager 2023-07-11 11:02:55 +02:00
Ensar Sarajčić 6fef121599 Fix formatting of senders and receivers 2023-07-11 09:55:57 +02:00
Ensar Sarajčić a5d6e7724c Reduce displayed data in message details dialog 2023-07-11 09:49:40 +02:00
Tibor Kaputa 1aa9a3a1a5
Merge pull request #681 from esensar/feature/33-blocked-keywords
Add support for blocking keywords for incoming messages
2023-07-10 20:20:07 +02:00
Tibor Kaputa b49643562b
updating the slovak translation 2023-07-10 20:19:33 +02:00
Ensar Sarajčić daea2d2e5d Remove unused address and subject from message filter in SmsReceiver 2023-07-10 17:00:32 +02:00
Ensar Sarajčić c1b29646d3 Show SIM in details only if multiple are present 2023-07-10 16:59:36 +02:00
Ensar Sarajčić 45416c07bd Split getReceiverOrSenderPhoneNumbers in MessageDetailsDialog in multiple lines 2023-07-10 16:51:37 +02:00
Ensar Sarajčić 28a19a09ce Use string format instead of concatenation in MessageDetailsDialog 2023-07-10 16:50:17 +02:00
Ensar Sarajčić e4269c8356 Remove needless extra empty line in ThreadAdapter 2023-07-10 16:47:50 +02:00
Ensar Sarajčić bdd506c96e Add message details menu button
Adds a "Properties" menu button in conversation, when one message
is selected, which displays details about the message:
- Type (SMS or MMS)
- Sender phone number (or receiver if it is a sent message)
- Used SIM
- Date sent at
- Date received at (if it is an incoming message)

This closes #19
2023-07-10 16:43:07 +02:00
Ensar Sarajčić 674a467694 Add support for blocking keywords for incoming messages
This adds support for filtering incoming messages based on
message body by checking if it contains any of the blocked keywords
(case insensitive). Regex and patterns are not supported at the moment.

NOTE: This does not currently support MMS, only SMS.

This closes #33
2023-07-10 14:11:41 +02:00
Tibor Kaputa 9942fb788a
Merge pull request #679 from wilsonrc/fix/675-refresh-cache-reply
[ISSUE-675] update last conversation message after replying from notification.
2023-07-08 09:46:23 +02:00
Wilson e3bf8541df update last conversation message after replying from notification.
#ISSUE-675
2023-07-07 15:13:23 -04:00
Tibor Kaputa 6540fe2aa7
Merge pull request #678 from esensar/fix/433-contact-details-mms-mixup
Properly look up participants in MMS group conversations
2023-07-07 12:49:45 +02:00
Ensar Sarajčić daf11dc6fd Properly look up participants in MMS group conversations
Application was always picking first participant when tapping on avatars
in conversations. It was also using first participant for MMS notifications.

This stores sender's phone number in the database, so it can be used to look
up correct participant in the list of participants. If matching on number fails,
matching on name is attempted. If both of these fail, it falls back to previous
behavior, which is just picking the first participant.

This may also be connected to #32, but I am not sure, since this should just
be related to behavior when tapping on avatars. Mixing up avatars in the
conversation should be a different issue.

This closes #433, closes #500, closes #384
2023-07-07 10:58:17 +02:00
Tibor Kaputa b0da7bad31
Merge pull request #672 from esensar/feature/446-password-protection
Add password protection feature to the app
2023-06-30 14:43:20 +02:00
Ensar Sarajčić 9ff6c3cbb6 Properly load threads on configuration changes 2023-06-30 10:56:07 +02:00
Ensar Sarajčić 8675de70c2 Handle app locking for threads too 2023-06-30 10:55:30 +02:00
Ensar Sarajčić ae2e480876 Prevent loading messages before app is unlocked 2023-06-30 10:41:01 +02:00
Ensar Sarajčić ce76573614 Adjust settings section label color properly 2023-06-30 10:25:45 +02:00
Ensar Sarajčić 143aaece4b Add password protection feature to the app
This closes #446
2023-06-30 09:29:36 +02:00
Tibor Kaputa af9143f46e
Merge pull request #670 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-06-26 23:07:05 +02:00
J. Lavoie 4bacfa4fa8
Translated using Weblate (Italian)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/it/
2023-06-26 23:03:48 +02:00
J. Lavoie f8982156fa
Translated using Weblate (French)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-06-26 23:03:48 +02:00
Tibor Kaputa 6dc79837d3
Merge pull request #668 from AAlier/master
Add Delete Action No Reply Notification message
2023-06-26 23:03:43 +02:00
Tibor Kaputa 73c12d0db1
removing an empty line 2023-06-26 22:48:13 +02:00
Alier 3a4f54b4be Remove extra translation 2023-06-27 02:40:41 +06:00
Alier a21662baef Update string translation 2023-06-26 16:27:23 +06:00
AAlier 5169539a88 Add missing code 2023-06-24 04:28:35 +06:00
Alier b076da3840 Add Delete Action Button to Sms message 2023-06-24 04:09:35 +06:00
tibbi 220dec1cd5 replacing jcenter with mavenCentral 2023-06-23 11:37:15 +02:00
tibbi 77768c66e0 fix #44, load less messages at conversations by default to speed it up 2023-06-21 12:22:11 +02:00
tibbi 4dfdb39603 adding some margin between conversations 2023-06-12 12:33:10 +02:00
tibbi 1d2443fda3 removing a confusing background at conversations, set it dynamically 2023-06-12 12:20:59 +02:00
tibbi e8d021a662 use nicer effects at long pressing conversations 2023-06-11 21:42:55 +02:00
Tibor Kaputa 32f6307464
Merge pull request #660 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-06-08 16:52:43 +02:00
Milo Ivir 08dd629b8b
Translated using Weblate (Croatian)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/hr/
2023-05-28 17:58:30 +02:00
Kryštof Černý ba518ea641
Translated using Weblate (Czech)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/cs/
2023-05-28 10:52:00 +02:00
gallegonovato 80428e2658
Translated using Weblate (Spanish)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/es/
2023-05-27 08:27:56 +02:00
Tibor Kaputa 063cd2bace
Merge pull request #658 from Aga-C/fix-deleting-notification
Fixed deleting notification after deleting conversation ( #637)
2023-05-24 10:15:05 +02:00
Tibor Kaputa 8d260d0b2e
Merge pull request #659 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-05-24 10:11:42 +02:00
en2sv 3074f28279
Translated using Weblate (Swedish)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-05-22 20:52:27 +02:00
Agnieszka C bb61229cef Fixed deleting notification after deleting conversation ( #637) 2023-05-20 08:35:03 +02:00
tibbi d168889767 updating changelog 2023-05-19 22:59:06 +02:00
tibbi 2bba900eb1 update version to 5.18.2 2023-05-19 22:59:00 +02:00
tibbi 16734aa93d Merge branch 'master' of github.com:SimpleMobileTools/Simple-SMS-Messenger 2023-05-19 22:54:22 +02:00
tibbi 3dadb0ccb8 updating commons and room 2023-05-19 22:54:13 +02:00
tibbi fb96109973 removing some extra margins at importing dialog 2023-05-19 22:53:59 +02:00
Tibor Kaputa dd48fa544e
Merge pull request #651 from yparitcher/history
refresh sms history based on oldest sms loaded, ignoring mms
2023-05-19 22:49:21 +02:00
Tibor Kaputa 6b7e8faefe
Merge pull request #655 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-05-19 22:48:23 +02:00
Milo Ivir fcfd0574bb
Translated using Weblate (Croatian)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/hr/
2023-05-19 22:48:06 +02:00
Priit Jõerüüt e7bc2c5e6d
Translated using Weblate (Estonian)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/et/
2023-05-19 22:48:06 +02:00
Tibor Kaputa 816d15b999
Merge pull request #656 from kcotugno/fix-json-import-partial-export
Fix import for partial exports
2023-05-19 22:48:01 +02:00
Tibor Kaputa e475069c42
autoformatting code 2023-05-19 22:47:21 +02:00
Kevin Cotugno da117ecca4 Fix import for partial exports
The previous implementation assumed that each JSON object would have an
"SMS" key and a "MMS" key, and in that order. This caused an exception
when it tried to read the next NAME token because the next token is
actually the closing bracket of the object. This commit fixes this issue
by checking that the next token is actually a NAME. If it is, we consume
the token and handle it according to its value, which may be either
"sms" or "mms". If it's neither of those, we skip it.

Fixes #646
2023-05-19 07:07:07 -07:00
Tibor Kaputa abedc2a18a
Merge pull request #642 from rmullin7286/master
Made ThreadActivity launch in singleTop mode and added onNewIntent.
2023-05-18 17:58:35 +02:00
Tibor Kaputa bfadbb42b5
Merge pull request #654 from spkprs/patch-15
Update strings.xml
2023-05-14 22:24:52 +02:00
Tibor Kaputa eb960c2a34
Merge pull request #653 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-05-14 22:24:36 +02:00
spkprs ccb61683ef
Update strings.xml 2023-05-14 18:52:46 +03:00
Sergio Marques dd02f4cf61
Translated using Weblate (Portuguese)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pt/
2023-05-13 23:52:08 +02:00
Tibor Kaputa 90ff57d877
Merge pull request #652 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-05-12 22:23:32 +02:00
Josep M. Ferrer a606058ad6
Translated using Weblate (Catalan)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ca/
2023-05-12 21:50:34 +02:00
Rex_sa 1abe0b84ae
Translated using Weblate (Arabic)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ar/
2023-05-12 21:50:33 +02:00
Eric 83397c508b
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-05-12 21:50:33 +02:00
Oğuz Ersen 0970c478eb
Translated using Weblate (Turkish)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/tr/
2023-05-12 21:50:33 +02:00
solokot ff9a96f32c
Translated using Weblate (Russian)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/ru/
2023-05-12 21:50:32 +02:00
Agnieszka C eabacec0b1
Translated using Weblate (Polish)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/pl/
2023-05-12 21:50:32 +02:00
Guillaume c66bdbf6c5
Translated using Weblate (Dutch)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nl/
2023-05-12 21:50:32 +02:00
VfBFan 05e4d8e42b
Translated using Weblate (German)
Currently translated at 100.0% (81 of 81 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/de/
2023-05-12 21:50:31 +02:00
yparitcher 707d7499e3
better method: limit the total of sms+mms to MESSAGES_LIMIT
this causes the sms and mms to load together for a smoother flow
2023-05-10 20:35:12 -04:00
tibbi 4596ab4475 use the new error string 2023-05-10 22:52:12 +02:00
tibbi 69e448f0a1 adding a new error message 2023-05-10 22:42:22 +02:00
tibbi 83c56c9f00 updating commons 2023-05-10 22:33:14 +02:00
Tibor Kaputa 055c1c7c08
Merge pull request #621 from Anis-cpu-13/fix-messaging-bug
Fix issue where sms_body was not being handled as an alternative to I…
2023-05-10 22:30:49 +02:00
Tibor Kaputa 616cab35f3
Merge pull request #625 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-05-10 22:24:54 +02:00
yparitcher 7b5d32e624
refresh sms history based on oldest sms loaded, ignoring mms
all mms are always loaded, the number of messages only concerns sms, so only reference sms to determine the oldest loaded message

this should load all sms messages when there are mms messages in between

Fixes: #535
2023-05-10 15:37:43 -04:00
FTno a57f8de092
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nb_NO/
2023-05-07 18:51:02 +02:00
FTno 4ca0144219
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/nb_NO/
2023-05-07 18:51:02 +02:00
en2sv e756fbf9c3
Translated using Weblate (Swedish)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-05-07 18:51:02 +02:00
Slávek Banko 4f140c5754
Translated using Weblate (Czech)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/cs/
2023-05-07 18:51:02 +02:00
tibbi a54155d358 updating commons 2023-05-07 18:50:55 +02:00
Ryan Mullin 41083ef754 Made ThreadActivity launch in singleTop mode and added onNewIntent. 2023-04-25 18:14:24 -07:00
tibbi 66c8b21cce changing version 5.18.0 to 5.18.1 in changelog 2023-04-14 10:20:18 +02:00
Anis-cpu-13 b4ea472884 Fix issue where sms_body was not being handled as an alternative to Intent.EXTRA_TEXT when sending SMS messages. Updated the launchThreadActivity function in NewConversationActivity.kt to handle sms_body as a fallback for message text. 2023-03-27 01:31:05 +02:00
tibbi fb0da99dc0 updating changelog 2023-03-25 09:13:57 +01:00
tibbi 06b435eae6 update version to 5.18.1 2023-03-25 09:13:15 +01:00
tibbi 197da1c7c9 Merge branch 'master' of github.com:SimpleMobileTools/Simple-SMS-Messenger 2023-03-25 09:12:39 +01:00
tibbi c3e9108132 updating changelog 2023-03-25 09:12:28 +01:00
tibbi 2ff719d2ac update version to 5.18.0 2023-03-25 09:12:22 +01:00
Tibor Kaputa d6f8d58867
Merge pull request #616 from kcotugno/stream-json-decode-on-import
Stream json during import fixes #329
2023-03-25 09:02:56 +01:00
tibbi 587250fd8d updating commons 2023-03-25 08:59:17 +01:00
Tibor Kaputa 8eb5d7e7af
Merge pull request #619 from Naveen3Singh/sms_mms_improvements
Fix MMS issues
2023-03-25 08:39:01 +01:00
Naveen Singh 8fb6f9ca83
Merge branch 'SimpleMobileTools:master' into sms_mms_improvements 2023-03-24 05:43:19 -07:00
Tibor Kaputa 2293a76045
Merge pull request #612 from weblate/weblate-simple-mobile-tools-simple-sms-messenger
Translations update from Hosted Weblate
2023-03-19 19:58:44 +01:00
Kevin Cotugno cdbb16bdc8 Stream json during import fixes #329 2023-03-17 13:32:45 -07:00
Milo Ivir 3f6f301dca
Translated using Weblate (Croatian)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/hr/
2023-03-16 15:37:47 +01:00
P.O d686b18ae5
Translated using Weblate (Swedish)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/sv/
2023-03-12 18:40:18 +01:00
Eric 160ac78220
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/zh_Hans/
2023-03-09 14:41:42 +01:00
J. Lavoie 4ab858f652
Translated using Weblate (Italian)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/it/
2023-03-07 23:41:28 +01:00
J. Lavoie 0198c14e82
Translated using Weblate (French)
Currently translated at 100.0% (80 of 80 strings)

Translation: Simple Mobile Tools/Simple SMS Messenger
Translate-URL: https://hosted.weblate.org/projects/simple-mobile-tools/simple-sms-messenger/fr/
2023-03-07 23:41:28 +01:00
Naveen abb7e66105 Allow sending only-text MMS messages
Fixes regression caused by recent changes in https://github.com/SimpleMobileTools/Simple-SMS-Messenger/pull/589
2023-03-06 14:28:11 +05:30
174 changed files with 6772 additions and 2139 deletions

View File

@ -1,6 +1,48 @@
Changelog
==========
Version 5.19.3 *(2023-10-05)*
----------------------------
* Allow archiving conversations
* Add an optional Recycle bin for messages
* Added some stability and translation improvements
Version 5.19.2 *(2023-10-04)*
----------------------------
* Allow archiving conversations
* Add an optional Recycle bin for messages
* Added some stability and translation improvements
Version 5.19.1 *(2023-09-20)*
----------------------------
* Allow archiving conversations
* Add an optional Recycle bin for messages
* Added some stability and translation improvements
Version 5.19.0 *(2023-09-19)*
----------------------------
* Allow archiving conversations
* Add an optional Recycle bin for messages
* Added some stability and translation improvements
Version 5.18.2 *(2023-05-19)*
----------------------------
* Fixed some smaller glitches at sending and receiving messages
* Fixed an issue at importing messages
* Added some stability and translation improvements
Version 5.18.1 *(2023-03-25)*
----------------------------
* Improved image resizing at sending
* Fixed an error with sending MMS
* Added some stability and translation improvements
Version 5.17.5 *(2023-03-04)*
----------------------------

View File

@ -28,7 +28,7 @@ Telegram:
https://t.me/SimpleMobileTools
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/images/button-google-play.svg' alt='Get it on Google Play' height=45/></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/images/button-f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/images/button-fdroid.svg' alt='Get it on F-Droid' height='45' /></a>
<div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.jpeg" width="30%">

View File

@ -1,78 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.simplemobiletools.smsmessenger"
minSdkVersion 23
targetSdkVersion 33
versionCode 73
versionName "5.17.5"
setProperty("archivesBaseName", "sms-messenger")
}
signingConfigs {
if (keystorePropertiesFile.exists()) {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
if (keystorePropertiesFile.exists()) {
signingConfig signingConfigs.release
}
}
}
flavorDimensions "variants"
productFlavors {
core {}
fdroid {}
prepaid {}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:fc000291b0'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61'
implementation 'com.github.tibbi:android-smsmms:33fcaf94d9'
implementation "me.leolin:ShortcutBadger:1.1.22"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
kapt "androidx.room:room-compiler:2.5.0"
implementation "androidx.room:room-runtime:2.5.0"
annotationProcessor "androidx.room:room-compiler:2.5.0"
}

111
app/build.gradle.kts Normal file
View File

@ -0,0 +1,111 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.konan.properties.Properties
import java.io.FileInputStream
plugins {
alias(libs.plugins.android)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.ksp)
base
}
base {
archivesName.set("sms-messenger")
}
val keystorePropertiesFile: File = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
compileSdk = project.libs.versions.app.build.compileSDKVersion.get().toInt()
defaultConfig {
applicationId = libs.versions.app.version.appId.get()
minSdk = project.libs.versions.app.build.minimumSDK.get().toInt()
targetSdk = project.libs.versions.app.build.targetSDK.get().toInt()
versionName = project.libs.versions.app.version.versionName.get()
versionCode = project.libs.versions.app.version.versionCode.get().toInt()
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildFeatures {
viewBinding = true
buildConfig = true
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
}
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("release")
}
}
}
flavorDimensions.add("variants")
productFlavors {
register("core")
register("fdroid")
register("prepaid")
}
sourceSets {
getByName("main").java.srcDirs("src/main/kotlin")
}
compileOptions {
val currentJavaVersionFromLibs = JavaVersion.valueOf(libs.versions.app.build.javaVersion.get().toString())
sourceCompatibility = currentJavaVersionFromLibs
targetCompatibility = currentJavaVersionFromLibs
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = project.libs.versions.app.build.kotlinJVMTarget.get()
}
namespace = libs.versions.app.version.appId.get()
lint {
checkReleaseBuilds = false
abortOnError = false
}
}
dependencies {
implementation(libs.simple.mobile.tools.commons)
implementation(libs.eventbus)
implementation(libs.indicator.fast.scroll)
implementation(libs.android.smsmms)
implementation(libs.shortcut.badger)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.lifecycle.process)
implementation(libs.ez.vcard)
implementation(libs.kotlinx.serialization.json)
implementation(libs.bundles.room)
ksp(libs.androidx.room.compiler)
}

View File

@ -4,3 +4,32 @@
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# Gson
-keep class com.simplemobiletools.commons.models.SimpleContact { *; }
-keep class com.simplemobiletools.smsmessenger.models.Attachment { *; }
-keep class com.simplemobiletools.smsmessenger.models.MessageAttachment { *; }

View File

@ -0,0 +1,342 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "23811e41b338a810cf5df26a5dff67a5",
"entities": [
{
"tableName": "conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` INTEGER NOT NULL, `snippet` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `title` TEXT NOT NULL, `photo_uri` TEXT NOT NULL, `is_group_conversation` INTEGER NOT NULL, `phone_number` TEXT NOT NULL, `is_scheduled` INTEGER NOT NULL, `uses_custom_title` INTEGER NOT NULL, `archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))",
"fields": [
{
"fieldPath": "threadId",
"columnName": "thread_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "snippet",
"columnName": "snippet",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "photoUri",
"columnName": "photo_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isGroupConversation",
"columnName": "is_group_conversation",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "phoneNumber",
"columnName": "phone_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isScheduled",
"columnName": "is_scheduled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "usesCustomTitle",
"columnName": "uses_custom_title",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isArchived",
"columnName": "archived",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"thread_id"
]
},
"indices": [
{
"name": "index_conversations_thread_id",
"unique": true,
"columnNames": [
"thread_id"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "attachments",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message_id` INTEGER NOT NULL, `uri_string` TEXT NOT NULL, `mimetype` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `filename` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "messageId",
"columnName": "message_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uriString",
"columnName": "uri_string",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mimetype",
"columnName": "mimetype",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "width",
"columnName": "width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "height",
"columnName": "height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "filename",
"columnName": "filename",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_attachments_message_id",
"unique": true,
"columnNames": [
"message_id"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_attachments_message_id` ON `${TABLE_NAME}` (`message_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "message_attachments",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `text` TEXT NOT NULL, `attachments` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "attachments",
"columnName": "attachments",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `body` TEXT NOT NULL, `type` INTEGER NOT NULL, `status` INTEGER NOT NULL, `participants` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `thread_id` INTEGER NOT NULL, `is_mms` INTEGER NOT NULL, `attachment` TEXT, `sender_phone_number` TEXT NOT NULL, `sender_name` TEXT NOT NULL, `sender_photo_uri` TEXT NOT NULL, `subscription_id` INTEGER NOT NULL, `is_scheduled` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "participants",
"columnName": "participants",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "threadId",
"columnName": "thread_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isMMS",
"columnName": "is_mms",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "attachment",
"columnName": "attachment",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "senderPhoneNumber",
"columnName": "sender_phone_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "senderName",
"columnName": "sender_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "senderPhotoUri",
"columnName": "sender_photo_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isScheduled",
"columnName": "is_scheduled",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "recycle_bin_messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `deleted_ts` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deletedTS",
"columnName": "deleted_ts",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_recycle_bin_messages_id",
"unique": true,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recycle_bin_messages_id` ON `${TABLE_NAME}` (`id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '23811e41b338a810cf5df26a5dff67a5')"
]
}
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.simplemobiletools.smsmessenger"
android:installLocation="auto">
<uses-permission android:name="android.permission.READ_SMS" />
@ -14,6 +13,7 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
@ -21,7 +21,7 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission
android:name="android.permission.USE_FINGERPRINT"
android:name="android.permission.USE_BIOMETRIC"
tools:node="remove" />
<queries>
@ -37,6 +37,7 @@
android:appCategory="productivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_launcher_name"
android:localeConfig="@xml/locale_config"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
@ -50,10 +51,25 @@
android:configChanges="orientation"
android:exported="true" />
<activity
android:name=".activities.RecycleBinConversationsActivity"
android:configChanges="orientation"
android:exported="true"
android:label="@string/recycle_bin"
android:parentActivityName=".activities.MainActivity" />
<activity
android:name=".activities.ArchivedConversationsActivity"
android:configChanges="orientation"
android:exported="true"
android:label="@string/archived_conversations"
android:parentActivityName=".activities.MainActivity" />
<activity
android:name=".activities.ThreadActivity"
android:configChanges="orientation"
android:exported="false"
android:launchMode="singleTop"
android:parentActivityName=".activities.MainActivity"
android:windowSoftInputMode="adjustResize" />
@ -124,6 +140,14 @@
android:configChanges="orientation"
android:exported="false"
android:label="@string/blocked_numbers"
android:parentActivityName=".activities.SettingsActivity"
tools:replace="android:label" />
<activity
android:name=".activities.ManageBlockedKeywordsActivity"
android:configChanges="orientation"
android:exported="false"
android:label="@string/blocked_keywords"
android:parentActivityName=".activities.SettingsActivity" />
<activity
@ -210,6 +234,15 @@
</intent-filter>
</receiver>
<receiver
android:name=".receivers.DeleteSmsReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.simplemobiletools.smsmessenger.action.delete" />
</intent-filter>
</receiver>
<receiver
android:name=".receivers.ScheduledMessageReceiver"
android:exported="false" />

View File

@ -0,0 +1,172 @@
package com.simplemobiletools.smsmessenger.activities
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ArchivedConversationsAdapter
import com.simplemobiletools.smsmessenger.databinding.ActivityArchivedConversationsBinding
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Events
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ArchivedConversationsActivity : SimpleActivity() {
private var bus: EventBus? = null
private val binding by viewBinding(ActivityArchivedConversationsBinding::inflate)
@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupOptionsMenu()
updateMaterialActivityViews(
mainCoordinatorLayout = binding.archiveCoordinator,
nestedView = binding.conversationsList,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.conversationsList, toolbar = binding.archiveToolbar)
loadArchivedConversations()
}
override fun onResume() {
super.onResume()
setupToolbar(binding.archiveToolbar, NavigationIcon.Arrow)
updateMenuColors()
loadArchivedConversations()
}
override fun onDestroy() {
super.onDestroy()
bus?.unregister(this)
}
private fun setupOptionsMenu() {
binding.archiveToolbar.inflateMenu(R.menu.archive_menu)
binding.archiveToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.empty_archive -> removeAll()
else -> return@setOnMenuItemClickListener false
}
return@setOnMenuItemClickListener true
}
}
private fun updateOptionsMenu(conversations: ArrayList<Conversation>) {
binding.archiveToolbar.menu.apply {
findItem(R.id.empty_archive).isVisible = conversations.isNotEmpty()
}
}
private fun updateMenuColors() {
updateStatusbarColor(getProperBackgroundColor())
}
private fun loadArchivedConversations() {
ensureBackgroundThread {
val conversations = try {
conversationsDB.getAllArchived().toMutableList() as ArrayList<Conversation>
} catch (e: Exception) {
ArrayList()
}
runOnUiThread {
setupConversations(conversations)
}
}
bus = EventBus.getDefault()
try {
bus!!.register(this)
} catch (ignored: Exception) {
}
}
private fun removeAll() {
ConfirmationDialog(
activity = this,
message = "",
messageId = R.string.empty_archive_confirmation,
positive = com.simplemobiletools.commons.R.string.yes,
negative = com.simplemobiletools.commons.R.string.no
) {
removeAllArchivedConversations {
loadArchivedConversations()
}
}
}
private fun getOrCreateConversationsAdapter(): ArchivedConversationsAdapter {
var currAdapter = binding.conversationsList.adapter
if (currAdapter == null) {
hideKeyboard()
currAdapter = ArchivedConversationsAdapter(
activity = this,
recyclerView = binding.conversationsList,
onRefresh = { notifyDatasetChanged() },
itemClick = { handleConversationClick(it) }
)
binding.conversationsList.adapter = currAdapter
if (areSystemAnimationsEnabled) {
binding.conversationsList.scheduleLayoutAnimation()
}
}
return currAdapter as ArchivedConversationsAdapter
}
private fun setupConversations(conversations: ArrayList<Conversation>) {
val sortedConversations = conversations.sortedWith(
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
.thenByDescending { it.date }
).toMutableList() as ArrayList<Conversation>
showOrHidePlaceholder(conversations.isEmpty())
updateOptionsMenu(conversations)
try {
getOrCreateConversationsAdapter().apply {
updateConversations(sortedConversations)
}
} catch (ignored: Exception) {
}
}
private fun showOrHidePlaceholder(show: Boolean) {
binding.conversationsFastscroller.beGoneIf(show)
binding.noConversationsPlaceholder.beVisibleIf(show)
binding.noConversationsPlaceholder.setTextColor(getProperTextColor())
binding.noConversationsPlaceholder.text = getString(R.string.no_archived_conversations)
}
@SuppressLint("NotifyDataSetChanged")
private fun notifyDatasetChanged() {
getOrCreateConversationsAdapter().notifyDataSetChanged()
}
private fun handleConversationClick(any: Any) {
Intent(this, ThreadActivity::class.java).apply {
val conversation = any as Conversation
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
putExtra(WAS_PROTECTION_HANDLED, true)
startActivity(this)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun refreshMessages(event: Events.RefreshMessages) {
loadArchivedConversations()
}
}

View File

@ -2,20 +2,16 @@ package com.simplemobiletools.smsmessenger.activities
import android.os.Bundle
import androidx.core.content.res.ResourcesCompat
import com.simplemobiletools.commons.extensions.applyColorFilter
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
import com.simplemobiletools.commons.extensions.getProperTextColor
import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.NavigationIcon
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter
import com.simplemobiletools.smsmessenger.databinding.ActivityConversationDetailsBinding
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.models.Conversation
import kotlinx.android.synthetic.main.activity_conversation_details.*
class ConversationDetailsActivity : SimpleActivity() {
@ -23,13 +19,20 @@ class ConversationDetailsActivity : SimpleActivity() {
private var conversation: Conversation? = null
private lateinit var participants: ArrayList<SimpleContact>
private val binding by viewBinding(ActivityConversationDetailsBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conversation_details)
setContentView(binding.root)
updateMaterialActivityViews(conversation_details_coordinator, participants_recyclerview, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(participants_recyclerview, conversation_details_toolbar)
updateMaterialActivityViews(
mainCoordinatorLayout = binding.conversationDetailsCoordinator,
nestedView = binding.participantsRecyclerview,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.participantsRecyclerview, toolbar = binding.conversationDetailsToolbar)
threadId = intent.getLongExtra(THREAD_ID, 0L)
ensureBackgroundThread {
@ -49,17 +52,17 @@ class ConversationDetailsActivity : SimpleActivity() {
override fun onResume() {
super.onResume()
setupToolbar(conversation_details_toolbar, NavigationIcon.Arrow)
updateTextColors(conversation_details_holder)
setupToolbar(binding.conversationDetailsToolbar, NavigationIcon.Arrow)
updateTextColors(binding.conversationDetailsHolder)
val primaryColor = getProperPrimaryColor()
conversation_name_heading.setTextColor(primaryColor)
members_heading.setTextColor(primaryColor)
binding.conversationNameHeading.setTextColor(primaryColor)
binding.membersHeading.setTextColor(primaryColor)
}
private fun setupTextViews() {
conversation_name.apply {
ResourcesCompat.getDrawable(resources, R.drawable.ic_edit_vector, theme)?.apply {
binding.conversationName.apply {
ResourcesCompat.getDrawable(resources, com.simplemobiletools.commons.R.drawable.ic_edit_vector, theme)?.apply {
applyColorFilter(getProperTextColor())
setCompoundDrawablesWithIntrinsicBounds(null, null, this, null)
}
@ -77,7 +80,7 @@ class ConversationDetailsActivity : SimpleActivity() {
}
private fun setupParticipants() {
val adapter = ContactsAdapter(this, participants, participants_recyclerview) {
val adapter = ContactsAdapter(this, participants, binding.participantsRecyclerview) {
val contact = it as SimpleContact
val address = contact.phoneNumbers.first().normalizedNumber
getContactFromAddress(address) { simpleContact ->
@ -86,6 +89,6 @@ class ConversationDetailsActivity : SimpleActivity() {
}
}
}
participants_recyclerview.adapter = adapter
binding.participantsRecyclerview.adapter = adapter
}
}

View File

@ -3,19 +3,16 @@ package com.simplemobiletools.smsmessenger.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.app.role.RoleManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Bundle
import android.provider.Telephony
import android.text.TextUtils
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FAQItem
@ -24,76 +21,67 @@ import com.simplemobiletools.smsmessenger.BuildConfig
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog
import com.simplemobiletools.smsmessenger.databinding.ActivityMainBinding
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Events
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.models.SearchResult
import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.FileOutputStream
import java.io.OutputStream
class MainActivity : SimpleActivity() {
private val MAKE_DEFAULT_APP_REQUEST = 1
private val PICK_IMPORT_SOURCE_INTENT = 11
private val PICK_EXPORT_FILE_INTENT = 21
private var storedTextColor = 0
private var storedFontSize = 0
private var lastSearchedText = ""
private var bus: EventBus? = null
private val smsExporter by lazy { MessagesExporter(this) }
private var wasProtectionHandled = false
private val binding by viewBinding(ActivityMainBinding::inflate)
@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContentView(binding.root)
appLaunched(BuildConfig.APPLICATION_ID)
setupOptionsMenu()
refreshMenuItems()
updateMaterialActivityViews(main_coordinator, conversations_list, useTransparentNavigation = true, useTopSearchMenu = true)
updateMaterialActivityViews(
mainCoordinatorLayout = binding.mainCoordinator,
nestedView = binding.conversationsList,
useTransparentNavigation = true,
useTopSearchMenu = true
)
if (savedInstanceState == null) {
checkAndDeleteOldRecycleBinMessages()
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
clearAllMessagesIfNeeded {
loadMessages()
}
} else {
finish()
}
}
}
if (checkAppSideloading()) {
return
}
if (isQPlus()) {
val roleManager = getSystemService(RoleManager::class.java)
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} else {
toast(R.string.unknown_error_occurred)
finish()
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {
askPermissions()
} else {
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
}
clearAllMessagesIfNeeded()
}
override fun onResume() {
super.onResume()
updateMenuColors()
refreshMenuItems()
getOrCreateConversationsAdapter().apply {
if (storedTextColor != getProperTextColor()) {
@ -107,18 +95,18 @@ class MainActivity : SimpleActivity() {
updateDrafts()
}
updateTextColors(main_coordinator)
search_holder.setBackgroundColor(getProperBackgroundColor())
updateTextColors(binding.mainCoordinator)
binding.searchHolder.setBackgroundColor(getProperBackgroundColor())
val properPrimaryColor = getProperPrimaryColor()
no_conversations_placeholder_2.setTextColor(properPrimaryColor)
no_conversations_placeholder_2.underlineText()
conversations_fastscroller.updateColors(properPrimaryColor)
conversations_progress_bar.setIndicatorColor(properPrimaryColor)
conversations_progress_bar.trackColor = properPrimaryColor.adjustAlpha(LOWER_ALPHA)
binding.noConversationsPlaceholder2.setTextColor(properPrimaryColor)
binding.noConversationsPlaceholder2.underlineText()
binding.conversationsFastscroller.updateColors(properPrimaryColor)
binding.conversationsProgressBar.setIndicatorColor(properPrimaryColor)
binding.conversationsProgressBar.trackColor = properPrimaryColor.adjustAlpha(LOWER_ALPHA)
checkShortcut()
(conversations_fab?.layoutParams as? CoordinatorLayout.LayoutParams)?.bottomMargin =
navigationBarHeight + resources.getDimension(R.dimen.activity_margin).toInt()
(binding.conversationsFab.layoutParams as? CoordinatorLayout.LayoutParams)?.bottomMargin =
navigationBarHeight + resources.getDimension(com.simplemobiletools.commons.R.dimen.activity_margin).toInt()
}
override fun onPause() {
@ -132,26 +120,49 @@ class MainActivity : SimpleActivity() {
}
override fun onBackPressed() {
if (main_menu.isSearchOpen) {
main_menu.closeSearch()
if (binding.mainMenu.isSearchOpen) {
binding.mainMenu.closeSearch()
} else {
super.onBackPressed()
}
}
private fun setupOptionsMenu() {
main_menu.getToolbar().inflateMenu(R.menu.menu_main)
main_menu.toggleHideOnScroll(true)
main_menu.setupMenu()
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(WAS_PROTECTION_HANDLED, wasProtectionHandled)
}
main_menu.onSearchClosedListener = {
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
wasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false)
if (!wasProtectionHandled) {
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
loadMessages()
} else {
finish()
}
}
} else {
loadMessages()
}
}
private fun setupOptionsMenu() {
binding.mainMenu.getToolbar().inflateMenu(R.menu.menu_main)
binding.mainMenu.toggleHideOnScroll(true)
binding.mainMenu.setupMenu()
binding.mainMenu.onSearchClosedListener = {
fadeOutSearch()
}
main_menu.onSearchTextChangedListener = { text ->
binding.mainMenu.onSearchTextChangedListener = { text ->
if (text.isNotEmpty()) {
if (search_holder.alpha < 1f) {
search_holder.fadeIn()
if (binding.searchHolder.alpha < 1f) {
binding.searchHolder.fadeIn()
}
} else {
fadeOutSearch()
@ -159,11 +170,11 @@ class MainActivity : SimpleActivity() {
searchTextChanged(text)
}
main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
binding.mainMenu.getToolbar().setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.import_messages -> tryImportMessages()
R.id.export_messages -> tryToExportMessages()
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
R.id.show_recycle_bin -> launchRecycleBin()
R.id.show_archived -> launchArchivedConversations()
R.id.settings -> launchSettings()
R.id.about -> launchAbout()
else -> return@setOnMenuItemClickListener false
@ -173,8 +184,10 @@ class MainActivity : SimpleActivity() {
}
private fun refreshMenuItems() {
main_menu.getToolbar().menu.apply {
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(R.bool.hide_google_relations)
binding.mainMenu.getToolbar().menu.apply {
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(com.simplemobiletools.commons.R.bool.hide_google_relations)
findItem(R.id.show_recycle_bin).isVisible = config.useRecycleBin
findItem(R.id.show_archived).isVisible = config.isArchiveAvailable
}
}
@ -186,11 +199,6 @@ class MainActivity : SimpleActivity() {
} else {
finish()
}
} else if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
tryImportMessagesFromFile(resultData.data!!)
} else if (requestCode == PICK_EXPORT_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
val outputStream = contentResolver.openOutputStream(resultData.data!!)
exportMessagesTo(outputStream)
}
}
@ -201,7 +209,32 @@ class MainActivity : SimpleActivity() {
private fun updateMenuColors() {
updateStatusbarColor(getProperBackgroundColor())
main_menu.updateColors()
binding.mainMenu.updateColors()
}
private fun loadMessages() {
if (isQPlus()) {
val roleManager = getSystemService(RoleManager::class.java)
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} else {
toast(com.simplemobiletools.commons.R.string.unknown_error_occurred)
finish()
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {
askPermissions()
} else {
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
}
}
// while SEND_SMS and READ_SMS permissions are mandatory, READ_CONTACTS is optional. If we don't have it, we just won't be able to show the contact name in some cases
@ -213,7 +246,10 @@ class MainActivity : SimpleActivity() {
handlePermission(PERMISSION_READ_CONTACTS) {
handleNotificationPermission { granted ->
if (!granted) {
toast(R.string.no_post_notifications_permissions)
PermissionRequiredDialog(
activity = this,
textId = com.simplemobiletools.commons.R.string.allow_notifications_incoming_messages,
positiveActionCallback = { openNotificationSettings() })
}
}
@ -221,7 +257,7 @@ class MainActivity : SimpleActivity() {
bus = EventBus.getDefault()
try {
bus!!.register(this)
} catch (e: Exception) {
} catch (ignored: Exception) {
}
}
} else {
@ -239,11 +275,11 @@ class MainActivity : SimpleActivity() {
storeStateVariables()
getCachedConversations()
no_conversations_placeholder_2.setOnClickListener {
binding.noConversationsPlaceholder2.setOnClickListener {
launchNewConversation()
}
conversations_fab.setOnClickListener {
binding.conversationsFab.setOnClickListener {
launchNewConversation()
}
}
@ -251,15 +287,21 @@ class MainActivity : SimpleActivity() {
private fun getCachedConversations() {
ensureBackgroundThread {
val conversations = try {
conversationsDB.getAll().toMutableList() as ArrayList<Conversation>
conversationsDB.getNonArchived().toMutableList() as ArrayList<Conversation>
} catch (e: Exception) {
ArrayList()
}
val archived = try {
conversationsDB.getAllArchived()
} catch (e: Exception) {
listOf()
}
updateUnreadCountBadge(conversations)
runOnUiThread {
setupConversations(conversations, cached = true)
getNewConversations(conversations)
getNewConversations((conversations + archived).toMutableList() as ArrayList<Conversation>)
}
conversations.forEach {
clearExpiredScheduledMessages(it.threadId)
@ -313,7 +355,7 @@ class MainActivity : SimpleActivity() {
}
}
val allConversations = conversationsDB.getAll() as ArrayList<Conversation>
val allConversations = conversationsDB.getNonArchived() as ArrayList<Conversation>
runOnUiThread {
setupConversations(allConversations)
}
@ -330,19 +372,19 @@ class MainActivity : SimpleActivity() {
}
private fun getOrCreateConversationsAdapter(): ConversationsAdapter {
var currAdapter = conversations_list.adapter
var currAdapter = binding.conversationsList.adapter
if (currAdapter == null) {
hideKeyboard()
currAdapter = ConversationsAdapter(
activity = this,
recyclerView = conversations_list,
recyclerView = binding.conversationsList,
onRefresh = { notifyDatasetChanged() },
itemClick = { handleConversationClick(it) }
)
conversations_list.adapter = currAdapter
binding.conversationsList.adapter = currAdapter
if (areSystemAnimationsEnabled) {
conversations_list.scheduleLayoutAnimation()
binding.conversationsList.scheduleLayoutAnimation()
}
}
return currAdapter as ConversationsAdapter
@ -376,25 +418,25 @@ class MainActivity : SimpleActivity() {
private fun showOrHideProgress(show: Boolean) {
if (show) {
conversations_progress_bar.show()
no_conversations_placeholder.beVisible()
no_conversations_placeholder.text = getString(R.string.loading_messages)
binding.conversationsProgressBar.show()
binding.noConversationsPlaceholder.beVisible()
binding.noConversationsPlaceholder.text = getString(R.string.loading_messages)
} else {
conversations_progress_bar.hide()
no_conversations_placeholder.beGone()
binding.conversationsProgressBar.hide()
binding.noConversationsPlaceholder.beGone()
}
}
private fun showOrHidePlaceholder(show: Boolean) {
conversations_fastscroller.beGoneIf(show)
no_conversations_placeholder.beVisibleIf(show)
no_conversations_placeholder.text = getString(R.string.no_conversations_found)
no_conversations_placeholder_2.beVisibleIf(show)
binding.conversationsFastscroller.beGoneIf(show)
binding.noConversationsPlaceholder.beVisibleIf(show)
binding.noConversationsPlaceholder.text = getString(R.string.no_conversations_found)
binding.noConversationsPlaceholder2.beVisibleIf(show)
}
private fun fadeOutSearch() {
search_holder.animate().alpha(0f).setDuration(SHORT_ANIMATION_DURATION).withEndAction {
search_holder.beGone()
binding.searchHolder.animate().alpha(0f).setDuration(SHORT_ANIMATION_DURATION).withEndAction {
binding.searchHolder.beGone()
searchTextChanged("", true)
}.start()
}
@ -409,6 +451,7 @@ class MainActivity : SimpleActivity() {
val conversation = any as Conversation
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
putExtra(WAS_PROTECTION_HANDLED, wasProtectionHandled)
startActivity(this)
}
}
@ -438,8 +481,8 @@ class MainActivity : SimpleActivity() {
@SuppressLint("NewApi")
private fun getCreateNewContactShortcut(appIconColor: Int): ShortcutInfo {
val newEvent = getString(R.string.new_conversation)
val drawable = resources.getDrawable(R.drawable.shortcut_plus)
(drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_plus_background).applyColorFilter(appIconColor)
val drawable = resources.getDrawable(com.simplemobiletools.commons.R.drawable.shortcut_plus)
(drawable as LayerDrawable).findDrawableByLayerId(com.simplemobiletools.commons.R.id.shortcut_plus_background).applyColorFilter(appIconColor)
val bmp = drawable.convertToBitmap()
val intent = Intent(this, NewConversationActivity::class.java)
@ -453,12 +496,12 @@ class MainActivity : SimpleActivity() {
}
private fun searchTextChanged(text: String, forceUpdate: Boolean = false) {
if (!main_menu.isSearchOpen && !forceUpdate) {
if (!binding.mainMenu.isSearchOpen && !forceUpdate) {
return
}
lastSearchedText = text
search_placeholder_2.beGoneIf(text.length >= 2)
binding.searchPlaceholder2.beGoneIf(text.length >= 2)
if (text.length >= 2) {
ensureBackgroundThread {
val searchQuery = "%$text%"
@ -469,8 +512,8 @@ class MainActivity : SimpleActivity() {
}
}
} else {
search_placeholder.beVisible()
search_results_list.beGone()
binding.searchPlaceholder.beVisible()
binding.searchResultsList.beGone()
}
}
@ -495,12 +538,12 @@ class MainActivity : SimpleActivity() {
}
runOnUiThread {
search_results_list.beVisibleIf(searchResults.isNotEmpty())
search_placeholder.beVisibleIf(searchResults.isEmpty())
binding.searchResultsList.beVisibleIf(searchResults.isNotEmpty())
binding.searchPlaceholder.beVisibleIf(searchResults.isEmpty())
val currAdapter = search_results_list.adapter
val currAdapter = binding.searchResultsList.adapter
if (currAdapter == null) {
SearchResultsAdapter(this, searchResults, search_results_list, searchedText) {
SearchResultsAdapter(this, searchResults, binding.searchResultsList, searchedText) {
hideKeyboard()
Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as SearchResult).threadId)
@ -509,7 +552,7 @@ class MainActivity : SimpleActivity() {
startActivity(this)
}
}.apply {
search_results_list.adapter = this
binding.searchResultsList.adapter = this
}
} else {
(currAdapter as SearchResultsAdapter).updateItems(searchResults, searchedText)
@ -517,6 +560,16 @@ class MainActivity : SimpleActivity() {
}
}
private fun launchRecycleBin() {
hideKeyboard()
startActivity(Intent(applicationContext, RecycleBinConversationsActivity::class.java))
}
private fun launchArchivedConversations() {
hideKeyboard()
startActivity(Intent(applicationContext, ArchivedConversationsActivity::class.java))
}
private fun launchSettings() {
hideKeyboard()
startActivity(Intent(applicationContext, SettingsActivity::class.java))
@ -528,117 +581,17 @@ class MainActivity : SimpleActivity() {
val faqItems = arrayListOf(
FAQItem(R.string.faq_2_title, R.string.faq_2_text),
FAQItem(R.string.faq_3_title, R.string.faq_3_text),
FAQItem(R.string.faq_9_title_commons, R.string.faq_9_text_commons)
FAQItem(com.simplemobiletools.commons.R.string.faq_9_title_commons, com.simplemobiletools.commons.R.string.faq_9_text_commons)
)
if (!resources.getBoolean(R.bool.hide_google_relations)) {
faqItems.add(FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons))
faqItems.add(FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons))
if (!resources.getBoolean(com.simplemobiletools.commons.R.bool.hide_google_relations)) {
faqItems.add(FAQItem(com.simplemobiletools.commons.R.string.faq_2_title_commons, com.simplemobiletools.commons.R.string.faq_2_text_commons))
faqItems.add(FAQItem(com.simplemobiletools.commons.R.string.faq_6_title_commons, com.simplemobiletools.commons.R.string.faq_6_text_commons))
}
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
}
private fun tryToExportMessages() {
if (isQPlus()) {
ExportMessagesDialog(this, config.lastExportPath, true) { file ->
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = EXPORT_MIME_TYPE
putExtra(Intent.EXTRA_TITLE, file.name)
addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(this, PICK_EXPORT_FILE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
} else {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
ExportMessagesDialog(this, config.lastExportPath, false) { file ->
getFileOutputStream(file.toFileDirItem(this), true) { outStream ->
exportMessagesTo(outStream)
}
}
}
}
}
}
private fun exportMessagesTo(outputStream: OutputStream?) {
toast(R.string.exporting)
ensureBackgroundThread {
smsExporter.exportMessages(outputStream) {
val toastId = when (it) {
MessagesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
else -> R.string.exporting_failed
}
toast(toastId)
}
}
}
private fun tryImportMessages() {
if (isQPlus()) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = EXPORT_MIME_TYPE
try {
startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
} else {
handlePermission(PERMISSION_READ_STORAGE) {
if (it) {
importEvents()
}
}
}
}
private fun importEvents() {
FilePickerDialog(this) {
showImportEventsDialog(it)
}
}
private fun showImportEventsDialog(path: String) {
ImportMessagesDialog(this, path)
}
private fun tryImportMessagesFromFile(uri: Uri) {
when (uri.scheme) {
"file" -> showImportEventsDialog(uri.path!!)
"content" -> {
val tempFile = getTempFile("messages", "backup.json")
if (tempFile == null) {
toast(R.string.unknown_error_occurred)
return
}
try {
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
showImportEventsDialog(tempFile.absolutePath)
} catch (e: Exception) {
showErrorToast(e)
}
}
else -> toast(R.string.invalid_file_format)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun refreshMessages(event: Events.RefreshMessages) {
initMessenger()

View File

@ -0,0 +1,97 @@
package com.simplemobiletools.smsmessenger.activities
import android.os.Bundle
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
import com.simplemobiletools.commons.extensions.underlineText
import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.commons.extensions.viewBinding
import com.simplemobiletools.commons.helpers.APP_ICON_IDS
import com.simplemobiletools.commons.helpers.APP_LAUNCHER_NAME
import com.simplemobiletools.commons.helpers.NavigationIcon
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.ActivityManageBlockedKeywordsBinding
import com.simplemobiletools.smsmessenger.dialogs.AddBlockedKeywordDialog
import com.simplemobiletools.smsmessenger.dialogs.ManageBlockedKeywordsAdapter
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.toArrayList
class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewListener {
override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
private val binding by viewBinding(ActivityManageBlockedKeywordsBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(binding.root)
updateBlockedKeywords()
setupOptionsMenu()
updateMaterialActivityViews(
mainCoordinatorLayout = binding.blockKeywordsCoordinator,
nestedView = binding.manageBlockedKeywordsList,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.manageBlockedKeywordsList, toolbar = binding.blockKeywordsToolbar)
updateTextColors(binding.manageBlockedKeywordsWrapper)
binding.manageBlockedKeywordsPlaceholder2.apply {
underlineText()
setTextColor(getProperPrimaryColor())
setOnClickListener {
addOrEditBlockedKeyword()
}
}
}
override fun onResume() {
super.onResume()
setupToolbar(binding.blockKeywordsToolbar, NavigationIcon.Arrow)
}
private fun setupOptionsMenu() {
binding.blockKeywordsToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.add_blocked_keyword -> {
addOrEditBlockedKeyword()
true
}
else -> false
}
}
}
override fun refreshItems() {
updateBlockedKeywords()
}
private fun updateBlockedKeywords() {
ensureBackgroundThread {
val blockedKeywords = config.blockedKeywords
runOnUiThread {
ManageBlockedKeywordsAdapter(this, blockedKeywords.toArrayList(), this, binding.manageBlockedKeywordsList) {
addOrEditBlockedKeyword(it as String)
}.apply {
binding.manageBlockedKeywordsList.adapter = this
}
binding.manageBlockedKeywordsPlaceholder.beVisibleIf(blockedKeywords.isEmpty())
binding.manageBlockedKeywordsPlaceholder2.beVisibleIf(blockedKeywords.isEmpty())
}
}
}
private fun addOrEditBlockedKeyword(keyword: String? = null) {
AddBlockedKeywordDialog(this, keyword) {
updateBlockedKeywords()
}
}
}

View File

@ -14,31 +14,38 @@ import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter
import com.simplemobiletools.smsmessenger.databinding.ActivityNewConversationBinding
import com.simplemobiletools.smsmessenger.databinding.ItemSuggestedContactBinding
import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts
import com.simplemobiletools.smsmessenger.extensions.getThreadId
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
import kotlinx.android.synthetic.main.activity_new_conversation.*
import kotlinx.android.synthetic.main.item_suggested_contact.view.*
import java.net.URLDecoder
import java.util.*
import java.util.Locale
class NewConversationActivity : SimpleActivity() {
private var allContacts = ArrayList<SimpleContact>()
private var privateContacts = ArrayList<SimpleContact>()
private val binding by viewBinding(ActivityNewConversationBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_conversation)
setContentView(binding.root)
title = getString(R.string.new_conversation)
updateTextColors(new_conversation_holder)
updateTextColors(binding.newConversationHolder)
updateMaterialActivityViews(new_conversation_coordinator, contacts_list, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(contacts_list, new_conversation_toolbar)
updateMaterialActivityViews(
mainCoordinatorLayout = binding.newConversationCoordinator,
nestedView = binding.contactsList,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.contactsList, toolbar = binding.newConversationToolbar)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
new_conversation_address.requestFocus()
binding.newConversationAddress.requestFocus()
// READ_CONTACTS permission is not mandatory, but without it we won't be able to show any suggestions during typing
handlePermission(PERMISSION_READ_CONTACTS) {
@ -48,10 +55,10 @@ class NewConversationActivity : SimpleActivity() {
override fun onResume() {
super.onResume()
setupToolbar(new_conversation_toolbar, NavigationIcon.Arrow)
no_contacts_placeholder_2.setTextColor(getProperPrimaryColor())
no_contacts_placeholder_2.underlineText()
suggestions_label.setTextColor(getProperPrimaryColor())
setupToolbar(binding.newConversationToolbar, NavigationIcon.Arrow)
binding.noContactsPlaceholder2.setTextColor(getProperPrimaryColor())
binding.noContactsPlaceholder2.underlineText()
binding.suggestionsLabel.setTextColor(getProperPrimaryColor())
}
private fun initContacts() {
@ -60,7 +67,7 @@ class NewConversationActivity : SimpleActivity() {
}
fetchContacts()
new_conversation_address.onTextChangeListener { searchString ->
binding.newConversationAddress.onTextChangeListener { searchString ->
val filteredContacts = ArrayList<SimpleContact>()
allContacts.forEach { contact ->
if (contact.phoneNumbers.any { it.normalizedNumber.contains(searchString, true) } ||
@ -74,21 +81,21 @@ class NewConversationActivity : SimpleActivity() {
filteredContacts.sortWith(compareBy { !it.name.startsWith(searchString, true) })
setupAdapter(filteredContacts)
new_conversation_confirm.beVisibleIf(searchString.length > 2)
binding.newConversationConfirm.beVisibleIf(searchString.length > 2)
}
new_conversation_confirm.applyColorFilter(getProperTextColor())
new_conversation_confirm.setOnClickListener {
val number = new_conversation_address.value
binding.newConversationConfirm.applyColorFilter(getProperTextColor())
binding.newConversationConfirm.setOnClickListener {
val number = binding.newConversationAddress.value
if (isShortCodeWithLetters(number)) {
new_conversation_address.setText("")
binding.newConversationAddress.setText("")
toast(R.string.invalid_short_code, length = Toast.LENGTH_LONG)
return@setOnClickListener
}
launchThreadActivity(number, number)
}
no_contacts_placeholder_2.setOnClickListener {
binding.noContactsPlaceholder2.setOnClickListener {
handlePermission(PERMISSION_READ_CONTACTS) {
if (it) {
fetchContacts()
@ -97,11 +104,11 @@ class NewConversationActivity : SimpleActivity() {
}
val properPrimaryColor = getProperPrimaryColor()
contacts_letter_fastscroller.textColor = getProperTextColor().getColorStateList()
contacts_letter_fastscroller.pressedTextColor = properPrimaryColor
contacts_letter_fastscroller_thumb.setupWithFastScroller(contacts_letter_fastscroller)
contacts_letter_fastscroller_thumb?.textColor = properPrimaryColor.getContrastColor()
contacts_letter_fastscroller_thumb?.thumbColor = properPrimaryColor.getColorStateList()
binding.contactsLetterFastscroller.textColor = getProperTextColor().getColorStateList()
binding.contactsLetterFastscroller.pressedTextColor = properPrimaryColor
binding.contactsLetterFastscrollerThumb.setupWithFastScroller(binding.contactsLetterFastscroller)
binding.contactsLetterFastscrollerThumb.textColor = properPrimaryColor.getContrastColor()
binding.contactsLetterFastscrollerThumb.thumbColor = properPrimaryColor.getColorStateList()
}
private fun isThirdPartyIntent(): Boolean {
@ -133,18 +140,23 @@ class NewConversationActivity : SimpleActivity() {
private fun setupAdapter(contacts: ArrayList<SimpleContact>) {
val hasContacts = contacts.isNotEmpty()
contacts_list.beVisibleIf(hasContacts)
no_contacts_placeholder.beVisibleIf(!hasContacts)
no_contacts_placeholder_2.beVisibleIf(!hasContacts && !hasPermission(PERMISSION_READ_CONTACTS))
binding.contactsList.beVisibleIf(hasContacts)
binding.noContactsPlaceholder.beVisibleIf(!hasContacts)
binding.noContactsPlaceholder2.beVisibleIf(!hasContacts && !hasPermission(PERMISSION_READ_CONTACTS))
if (!hasContacts) {
val placeholderText = if (hasPermission(PERMISSION_READ_CONTACTS)) R.string.no_contacts_found else R.string.no_access_to_contacts
no_contacts_placeholder.text = getString(placeholderText)
val placeholderText = if (hasPermission(PERMISSION_READ_CONTACTS)) {
com.simplemobiletools.commons.R.string.no_contacts_found
} else {
com.simplemobiletools.commons.R.string.no_access_to_contacts
}
binding.noContactsPlaceholder.text = getString(placeholderText)
}
val currAdapter = contacts_list.adapter
val currAdapter = binding.contactsList.adapter
if (currAdapter == null) {
ContactsAdapter(this, contacts, contacts_list) {
ContactsAdapter(this, contacts, binding.contactsList) {
hideKeyboard()
val contact = it as SimpleContact
val phoneNumbers = contact.phoneNumbers
@ -167,11 +179,11 @@ class NewConversationActivity : SimpleActivity() {
launchThreadActivity(phoneNumbers.first().normalizedNumber, contact.name)
}
}.apply {
contacts_list.adapter = this
binding.contactsList.adapter = this
}
if (areSystemAnimationsEnabled) {
contacts_list.scheduleLayoutAnimation()
binding.contactsList.scheduleLayoutAnimation()
}
} else {
(currAdapter as ContactsAdapter).updateContacts(contacts)
@ -186,23 +198,23 @@ class NewConversationActivity : SimpleActivity() {
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
val suggestions = getSuggestedContacts(privateContacts)
runOnUiThread {
suggestions_holder.removeAllViews()
binding.suggestionsHolder.removeAllViews()
if (suggestions.isEmpty()) {
suggestions_label.beGone()
suggestions_scrollview.beGone()
binding.suggestionsLabel.beGone()
binding.suggestionsScrollview.beGone()
} else {
suggestions_label.beVisible()
suggestions_scrollview.beVisible()
binding.suggestionsLabel.beVisible()
binding.suggestionsScrollview.beVisible()
suggestions.forEach {
val contact = it
layoutInflater.inflate(R.layout.item_suggested_contact, null).apply {
suggested_contact_name.text = contact.name
suggested_contact_name.setTextColor(getProperTextColor())
ItemSuggestedContactBinding.inflate(layoutInflater).apply {
suggestedContactName.text = contact.name
suggestedContactName.setTextColor(getProperTextColor())
if (!isDestroyed) {
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, contact.name)
suggestions_holder.addView(this)
setOnClickListener {
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggestedContactImage, contact.name)
binding.suggestionsHolder.addView(root)
root.setOnClickListener {
launchThreadActivity(contact.phoneNumbers.first().normalizedNumber, contact.name)
}
}
@ -215,11 +227,11 @@ class NewConversationActivity : SimpleActivity() {
}
private fun setupLetterFastscroller(contacts: ArrayList<SimpleContact>) {
contacts_letter_fastscroller.setupWithRecyclerView(contacts_list, { position ->
binding.contactsLetterFastscroller.setupWithRecyclerView(binding.contactsList, { position ->
try {
val name = contacts[position].name
val character = if (name.isNotEmpty()) name.substring(0, 1) else ""
FastScrollItemIndicator.Text(character.toUpperCase(Locale.getDefault()).normalizeString())
FastScrollItemIndicator.Text(character.uppercase(Locale.getDefault()).normalizeString())
} catch (e: Exception) {
FastScrollItemIndicator.Text("")
}
@ -228,7 +240,7 @@ class NewConversationActivity : SimpleActivity() {
private fun launchThreadActivity(phoneNumber: String, name: String) {
hideKeyboard()
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: intent.getStringExtra("sms_body") ?: ""
val numbers = phoneNumber.split(";").toSet()
val number = if (numbers.size == 1) phoneNumber else Gson().toJson(numbers)
Intent(this, ThreadActivity::class.java).apply {

View File

@ -0,0 +1,173 @@
package com.simplemobiletools.smsmessenger.activities
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.RecycleBinConversationsAdapter
import com.simplemobiletools.smsmessenger.databinding.ActivityRecycleBinConversationsBinding
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Events
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class RecycleBinConversationsActivity : SimpleActivity() {
private var bus: EventBus? = null
private val binding by viewBinding(ActivityRecycleBinConversationsBinding::inflate)
@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupOptionsMenu()
updateMaterialActivityViews(
mainCoordinatorLayout = binding.recycleBinCoordinator,
nestedView = binding.conversationsList,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.conversationsList, toolbar = binding.recycleBinToolbar)
loadRecycleBinConversations()
}
override fun onResume() {
super.onResume()
setupToolbar(binding.recycleBinToolbar, NavigationIcon.Arrow)
updateMenuColors()
loadRecycleBinConversations()
}
override fun onDestroy() {
super.onDestroy()
bus?.unregister(this)
}
private fun setupOptionsMenu() {
binding.recycleBinToolbar.inflateMenu(R.menu.recycle_bin_menu)
binding.recycleBinToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.empty_recycle_bin -> removeAll()
else -> return@setOnMenuItemClickListener false
}
return@setOnMenuItemClickListener true
}
}
private fun updateOptionsMenu(conversations: ArrayList<Conversation>) {
binding.recycleBinToolbar.menu.apply {
findItem(R.id.empty_recycle_bin).isVisible = conversations.isNotEmpty()
}
}
private fun updateMenuColors() {
updateStatusbarColor(getProperBackgroundColor())
}
private fun loadRecycleBinConversations() {
ensureBackgroundThread {
val conversations = try {
conversationsDB.getAllWithMessagesInRecycleBin().toMutableList() as ArrayList<Conversation>
} catch (e: Exception) {
ArrayList()
}
runOnUiThread {
setupConversations(conversations)
}
}
bus = EventBus.getDefault()
try {
bus!!.register(this)
} catch (ignored: Exception) {
}
}
private fun removeAll() {
ConfirmationDialog(
activity = this,
message = "",
messageId = R.string.empty_recycle_bin_messages_confirmation,
positive = com.simplemobiletools.commons.R.string.yes,
negative = com.simplemobiletools.commons.R.string.no
) {
ensureBackgroundThread {
emptyMessagesRecycleBin()
loadRecycleBinConversations()
}
}
}
private fun getOrCreateConversationsAdapter(): RecycleBinConversationsAdapter {
var currAdapter = binding.conversationsList.adapter
if (currAdapter == null) {
hideKeyboard()
currAdapter = RecycleBinConversationsAdapter(
activity = this,
recyclerView = binding.conversationsList,
onRefresh = { notifyDatasetChanged() },
itemClick = { handleConversationClick(it) }
)
binding.conversationsList.adapter = currAdapter
if (areSystemAnimationsEnabled) {
binding.conversationsList.scheduleLayoutAnimation()
}
}
return currAdapter as RecycleBinConversationsAdapter
}
private fun setupConversations(conversations: ArrayList<Conversation>) {
val sortedConversations = conversations.sortedWith(
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
.thenByDescending { it.date }
).toMutableList() as ArrayList<Conversation>
showOrHidePlaceholder(conversations.isEmpty())
updateOptionsMenu(conversations)
try {
getOrCreateConversationsAdapter().apply {
updateConversations(sortedConversations)
}
} catch (ignored: Exception) {
}
}
private fun showOrHidePlaceholder(show: Boolean) {
binding.conversationsFastscroller.beGoneIf(show)
binding.noConversationsPlaceholder.beVisibleIf(show)
binding.noConversationsPlaceholder.text = getString(R.string.no_conversations_found)
}
@SuppressLint("NotifyDataSetChanged")
private fun notifyDatasetChanged() {
getOrCreateConversationsAdapter().notifyDataSetChanged()
}
private fun handleConversationClick(any: Any) {
Intent(this, ThreadActivity::class.java).apply {
val conversation = any as Conversation
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
putExtra(WAS_PROTECTION_HANDLED, true)
putExtra(IS_RECYCLE_BIN, true)
startActivity(this)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun refreshMessages(event: Events.RefreshMessages) {
loadRecycleBinConversations()
}
}

View File

@ -2,36 +2,53 @@ package com.simplemobiletools.smsmessenger.activities
import android.annotation.TargetApi
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
import com.simplemobiletools.commons.dialogs.ChangeDateTimeFormatDialog
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.dialogs.*
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.ActivitySettingsBinding
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.emptyMessagesRecycleBin
import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.helpers.*
import kotlinx.android.synthetic.main.activity_settings.*
import com.simplemobiletools.smsmessenger.models.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.*
import kotlin.system.exitProcess
class SettingsActivity : SimpleActivity() {
private var blockedNumbersAtPause = -1
private var recycleBinMessages = 0
private val messagesFileType = "application/json"
private val messageImportFileTypes = listOf("application/json", "application/xml", "text/xml")
private val binding by viewBinding(ActivitySettingsBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setContentView(binding.root)
updateMaterialActivityViews(settings_coordinator, settings_holder, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(settings_nested_scrollview, settings_toolbar)
updateMaterialActivityViews(
mainCoordinatorLayout = binding.settingsCoordinator,
nestedView = binding.settingsHolder,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(scrollingView = binding.settingsNestedScrollview, toolbar = binding.settingsToolbar)
}
override fun onResume() {
super.onResume()
setupToolbar(settings_toolbar, NavigationIcon.Arrow)
setupToolbar(binding.settingsToolbar, NavigationIcon.Arrow)
setupPurchaseThankYou()
setupCustomizeColors()
@ -39,6 +56,7 @@ class SettingsActivity : SimpleActivity() {
setupUseEnglish()
setupLanguage()
setupManageBlockedNumbers()
setupManageBlockedKeywords()
setupChangeDateTimeFormat()
setupFontSize()
setupShowCharacterCounter()
@ -49,168 +67,240 @@ class SettingsActivity : SimpleActivity() {
setupGroupMessageAsMMS()
setupLockScreenVisibility()
setupMMSFileSizeLimit()
updateTextColors(settings_nested_scrollview)
setupUseRecycleBin()
setupEmptyRecycleBin()
setupAppPasswordProtection()
setupMessagesExport()
setupMessagesImport()
updateTextColors(binding.settingsNestedScrollview)
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
refreshMessages()
}
arrayOf(
settings_color_customization_section_label,
settings_general_settings_label,
settings_outgoing_messages_label,
settings_notifications_label
binding.settingsColorCustomizationSectionLabel,
binding.settingsGeneralSettingsLabel,
binding.settingsOutgoingMessagesLabel,
binding.settingsNotificationsLabel,
binding.settingsRecycleBinLabel,
binding.settingsSecurityLabel,
binding.settingsMigratingLabel
).forEach {
it.setTextColor(getProperPrimaryColor())
}
}
private val getContent = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) {
MessagesImporter(this).importMessages(uri)
}
}
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(messagesFileType)) { uri ->
if (uri != null) {
toast(com.simplemobiletools.commons.R.string.exporting)
exportMessages(uri)
}
}
private fun setupMessagesExport() {
binding.settingsExportMessagesHolder.setOnClickListener {
ExportMessagesDialog(this) { fileName ->
saveDocument.launch(fileName)
}
}
}
private fun setupMessagesImport() {
binding.settingsImportMessagesHolder.setOnClickListener {
getContent.launch(messageImportFileTypes.toTypedArray())
}
}
private fun exportMessages(uri: Uri) {
ensureBackgroundThread {
try {
MessagesReader(this).getMessagesToExport(config.exportSms, config.exportMms) { messagesToExport ->
if (messagesToExport.isEmpty()) {
toast(com.simplemobiletools.commons.R.string.no_entries_for_exporting)
return@getMessagesToExport
}
val json = Json { encodeDefaults = true }
val jsonString = json.encodeToString(messagesToExport)
val outputStream = contentResolver.openOutputStream(uri)!!
outputStream.use {
it.write(jsonString.toByteArray())
}
toast(com.simplemobiletools.commons.R.string.exporting_successful)
}
} catch (e: Exception) {
showErrorToast(e)
}
}
}
override fun onPause() {
super.onPause()
blockedNumbersAtPause = getBlockedNumbers().hashCode()
}
private fun setupPurchaseThankYou() {
settings_purchase_thank_you_holder.beGoneIf(isOrWasThankYouInstalled())
settings_purchase_thank_you_holder.setOnClickListener {
private fun setupPurchaseThankYou() = binding.apply {
settingsPurchaseThankYouHolder.beGoneIf(isOrWasThankYouInstalled())
settingsPurchaseThankYouHolder.setOnClickListener {
launchPurchaseThankYouIntent()
}
}
private fun setupCustomizeColors() {
settings_color_customization_label.text = getCustomizeColorsString()
settings_color_customization_holder.setOnClickListener {
private fun setupCustomizeColors() = binding.apply {
settingsColorCustomizationLabel.text = getCustomizeColorsString()
settingsColorCustomizationHolder.setOnClickListener {
handleCustomizeColorsClick()
}
}
private fun setupCustomizeNotifications() {
settings_customize_notifications_holder.beVisibleIf(isOreoPlus())
settings_customize_notifications_holder.setOnClickListener {
private fun setupCustomizeNotifications() = binding.apply {
settingsCustomizeNotificationsHolder.beVisibleIf(isOreoPlus())
settingsCustomizeNotificationsHolder.setOnClickListener {
launchCustomizeNotificationsIntent()
}
}
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus())
settings_use_english.isChecked = config.useEnglish
settings_use_english_holder.setOnClickListener {
settings_use_english.toggle()
config.useEnglish = settings_use_english.isChecked
System.exit(0)
private fun setupUseEnglish() = binding.apply {
settingsUseEnglishHolder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus())
settingsUseEnglish.isChecked = config.useEnglish
settingsUseEnglishHolder.setOnClickListener {
settingsUseEnglish.toggle()
config.useEnglish = settingsUseEnglish.isChecked
exitProcess(0)
}
}
private fun setupLanguage() {
settings_language.text = Locale.getDefault().displayLanguage
settings_language_holder.beVisibleIf(isTiramisuPlus())
settings_language_holder.setOnClickListener {
private fun setupLanguage() = binding.apply {
settingsLanguage.text = Locale.getDefault().displayLanguage
settingsLanguageHolder.beVisibleIf(isTiramisuPlus())
settingsLanguageHolder.setOnClickListener {
launchChangeAppLanguageIntent()
}
}
// support for device-wise blocking came on Android 7, rely only on that
@TargetApi(Build.VERSION_CODES.N)
private fun setupManageBlockedNumbers() {
settings_manage_blocked_numbers.text = addLockedLabelIfNeeded(R.string.manage_blocked_numbers)
settings_manage_blocked_numbers_holder.beVisibleIf(isNougatPlus())
private fun setupManageBlockedNumbers() = binding.apply {
settingsManageBlockedNumbers.text = addLockedLabelIfNeeded(com.simplemobiletools.commons.R.string.manage_blocked_numbers)
settingsManageBlockedNumbersHolder.beVisibleIf(isNougatPlus())
settings_manage_blocked_numbers_holder.setOnClickListener {
settingsManageBlockedNumbersHolder.setOnClickListener {
if (isOrWasThankYouInstalled()) {
Intent(this, ManageBlockedNumbersActivity::class.java).apply {
Intent(this@SettingsActivity, ManageBlockedNumbersActivity::class.java).apply {
startActivity(this)
}
} else {
FeatureLockedDialog(this) { }
FeatureLockedDialog(this@SettingsActivity) { }
}
}
}
private fun setupChangeDateTimeFormat() {
settings_change_date_time_format_holder.setOnClickListener {
ChangeDateTimeFormatDialog(this) {
private fun setupManageBlockedKeywords() = binding.apply {
settingsManageBlockedKeywords.text = addLockedLabelIfNeeded(R.string.manage_blocked_keywords)
settingsManageBlockedKeywordsHolder.setOnClickListener {
if (isOrWasThankYouInstalled()) {
Intent(this@SettingsActivity, ManageBlockedKeywordsActivity::class.java).apply {
startActivity(this)
}
} else {
FeatureLockedDialog(this@SettingsActivity) { }
}
}
}
private fun setupChangeDateTimeFormat() = binding.apply {
settingsChangeDateTimeFormatHolder.setOnClickListener {
ChangeDateTimeFormatDialog(this@SettingsActivity) {
refreshMessages()
}
}
}
private fun setupFontSize() {
settings_font_size.text = getFontSizeText()
settings_font_size_holder.setOnClickListener {
private fun setupFontSize() = binding.apply {
settingsFontSize.text = getFontSizeText()
settingsFontSizeHolder.setOnClickListener {
val items = arrayListOf(
RadioItem(FONT_SIZE_SMALL, getString(R.string.small)),
RadioItem(FONT_SIZE_MEDIUM, getString(R.string.medium)),
RadioItem(FONT_SIZE_LARGE, getString(R.string.large)),
RadioItem(FONT_SIZE_EXTRA_LARGE, getString(R.string.extra_large))
RadioItem(FONT_SIZE_SMALL, getString(com.simplemobiletools.commons.R.string.small)),
RadioItem(FONT_SIZE_MEDIUM, getString(com.simplemobiletools.commons.R.string.medium)),
RadioItem(FONT_SIZE_LARGE, getString(com.simplemobiletools.commons.R.string.large)),
RadioItem(FONT_SIZE_EXTRA_LARGE, getString(com.simplemobiletools.commons.R.string.extra_large))
)
RadioGroupDialog(this@SettingsActivity, items, config.fontSize) {
config.fontSize = it as Int
settings_font_size.text = getFontSizeText()
settingsFontSize.text = getFontSizeText()
}
}
}
private fun setupShowCharacterCounter() {
settings_show_character_counter.isChecked = config.showCharacterCounter
settings_show_character_counter_holder.setOnClickListener {
settings_show_character_counter.toggle()
config.showCharacterCounter = settings_show_character_counter.isChecked
private fun setupShowCharacterCounter() = binding.apply {
settingsShowCharacterCounter.isChecked = config.showCharacterCounter
settingsShowCharacterCounterHolder.setOnClickListener {
settingsShowCharacterCounter.toggle()
config.showCharacterCounter = settingsShowCharacterCounter.isChecked
}
}
private fun setupUseSimpleCharacters() {
settings_use_simple_characters.isChecked = config.useSimpleCharacters
settings_use_simple_characters_holder.setOnClickListener {
settings_use_simple_characters.toggle()
config.useSimpleCharacters = settings_use_simple_characters.isChecked
private fun setupUseSimpleCharacters() = binding.apply {
settingsUseSimpleCharacters.isChecked = config.useSimpleCharacters
settingsUseSimpleCharactersHolder.setOnClickListener {
settingsUseSimpleCharacters.toggle()
config.useSimpleCharacters = settingsUseSimpleCharacters.isChecked
}
}
private fun setupSendOnEnter() {
settings_send_on_enter.isChecked = config.sendOnEnter
settings_send_on_enter_holder.setOnClickListener {
settings_send_on_enter.toggle()
config.sendOnEnter = settings_send_on_enter.isChecked
private fun setupSendOnEnter() = binding.apply {
settingsSendOnEnter.isChecked = config.sendOnEnter
settingsSendOnEnterHolder.setOnClickListener {
settingsSendOnEnter.toggle()
config.sendOnEnter = settingsSendOnEnter.isChecked
}
}
private fun setupEnableDeliveryReports() {
settings_enable_delivery_reports.isChecked = config.enableDeliveryReports
settings_enable_delivery_reports_holder.setOnClickListener {
settings_enable_delivery_reports.toggle()
config.enableDeliveryReports = settings_enable_delivery_reports.isChecked
private fun setupEnableDeliveryReports() = binding.apply {
settingsEnableDeliveryReports.isChecked = config.enableDeliveryReports
settingsEnableDeliveryReportsHolder.setOnClickListener {
settingsEnableDeliveryReports.toggle()
config.enableDeliveryReports = settingsEnableDeliveryReports.isChecked
}
}
private fun setupSendLongMessageAsMMS() {
settings_send_long_message_mms.isChecked = config.sendLongMessageMMS
settings_send_long_message_mms_holder.setOnClickListener {
settings_send_long_message_mms.toggle()
config.sendLongMessageMMS = settings_send_long_message_mms.isChecked
private fun setupSendLongMessageAsMMS() = binding.apply {
settingsSendLongMessageMms.isChecked = config.sendLongMessageMMS
settingsSendLongMessageMmsHolder.setOnClickListener {
settingsSendLongMessageMms.toggle()
config.sendLongMessageMMS = settingsSendLongMessageMms.isChecked
}
}
private fun setupGroupMessageAsMMS() {
settings_send_group_message_mms.isChecked = config.sendGroupMessageMMS
settings_send_group_message_mms_holder.setOnClickListener {
settings_send_group_message_mms.toggle()
config.sendGroupMessageMMS = settings_send_group_message_mms.isChecked
private fun setupGroupMessageAsMMS() = binding.apply {
settingsSendGroupMessageMms.isChecked = config.sendGroupMessageMMS
settingsSendGroupMessageMmsHolder.setOnClickListener {
settingsSendGroupMessageMms.toggle()
config.sendGroupMessageMMS = settingsSendGroupMessageMms.isChecked
}
}
private fun setupLockScreenVisibility() {
settings_lock_screen_visibility.text = getLockScreenVisibilityText()
settings_lock_screen_visibility_holder.setOnClickListener {
private fun setupLockScreenVisibility() = binding.apply {
settingsLockScreenVisibility.text = getLockScreenVisibilityText()
settingsLockScreenVisibilityHolder.setOnClickListener {
val items = arrayListOf(
RadioItem(LOCK_SCREEN_SENDER_MESSAGE, getString(R.string.sender_and_message)),
RadioItem(LOCK_SCREEN_SENDER, getString(R.string.sender_only)),
RadioItem(LOCK_SCREEN_NOTHING, getString(R.string.nothing)),
RadioItem(LOCK_SCREEN_NOTHING, getString(com.simplemobiletools.commons.R.string.nothing)),
)
RadioGroupDialog(this@SettingsActivity, items, config.lockScreenVisibilitySetting) {
config.lockScreenVisibilitySetting = it as Int
settings_lock_screen_visibility.text = getLockScreenVisibilityText()
settingsLockScreenVisibility.text = getLockScreenVisibilityText()
}
}
}
@ -219,13 +309,13 @@ class SettingsActivity : SimpleActivity() {
when (config.lockScreenVisibilitySetting) {
LOCK_SCREEN_SENDER_MESSAGE -> R.string.sender_and_message
LOCK_SCREEN_SENDER -> R.string.sender_only
else -> R.string.nothing
else -> com.simplemobiletools.commons.R.string.nothing
}
)
private fun setupMMSFileSizeLimit() {
settings_mms_file_size_limit.text = getMMSFileLimitText()
settings_mms_file_size_limit_holder.setOnClickListener {
private fun setupMMSFileSizeLimit() = binding.apply {
settingsMmsFileSizeLimit.text = getMMSFileLimitText()
settingsMmsFileSizeLimitHolder.setOnClickListener {
val items = arrayListOf(
RadioItem(7, getString(R.string.mms_file_size_limit_none), FILE_SIZE_NONE),
RadioItem(6, getString(R.string.mms_file_size_limit_2mb), FILE_SIZE_2_MB),
@ -239,7 +329,78 @@ class SettingsActivity : SimpleActivity() {
val checkedItemId = items.find { it.value == config.mmsFileSizeLimit }?.id ?: 7
RadioGroupDialog(this@SettingsActivity, items, checkedItemId) {
config.mmsFileSizeLimit = it as Long
settings_mms_file_size_limit.text = getMMSFileLimitText()
settingsMmsFileSizeLimit.text = getMMSFileLimitText()
}
}
}
private fun setupUseRecycleBin() = binding.apply {
updateRecycleBinButtons()
settingsUseRecycleBin.isChecked = config.useRecycleBin
settingsUseRecycleBinHolder.setOnClickListener {
settingsUseRecycleBin.toggle()
config.useRecycleBin = settingsUseRecycleBin.isChecked
updateRecycleBinButtons()
}
}
private fun updateRecycleBinButtons() = binding.apply {
settingsEmptyRecycleBinHolder.beVisibleIf(config.useRecycleBin)
}
private fun setupEmptyRecycleBin() = binding.apply {
ensureBackgroundThread {
recycleBinMessages = messagesDB.getArchivedCount()
runOnUiThread {
settingsEmptyRecycleBinSize.text =
resources.getQuantityString(R.plurals.delete_messages, recycleBinMessages, recycleBinMessages)
}
}
settingsEmptyRecycleBinHolder.setOnClickListener {
if (recycleBinMessages == 0) {
toast(com.simplemobiletools.commons.R.string.recycle_bin_empty)
} else {
ConfirmationDialog(
activity = this@SettingsActivity,
message = "",
messageId = R.string.empty_recycle_bin_messages_confirmation,
positive = com.simplemobiletools.commons.R.string.yes,
negative = com.simplemobiletools.commons.R.string.no
) {
ensureBackgroundThread {
emptyMessagesRecycleBin()
}
recycleBinMessages = 0
settingsEmptyRecycleBinSize.text =
resources.getQuantityString(R.plurals.delete_messages, recycleBinMessages, recycleBinMessages)
}
}
}
}
private fun setupAppPasswordProtection() = binding.apply {
settingsAppPasswordProtection.isChecked = config.isAppPasswordProtectionOn
settingsAppPasswordProtectionHolder.setOnClickListener {
val tabToShow = if (config.isAppPasswordProtectionOn) config.appProtectionType else SHOW_ALL_TABS
SecurityDialog(this@SettingsActivity, config.appPasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isAppPasswordProtectionOn
settingsAppPasswordProtection.isChecked = !hasPasswordProtection
config.isAppPasswordProtectionOn = !hasPasswordProtection
config.appPasswordHash = if (hasPasswordProtection) "" else hash
config.appProtectionType = type
if (config.isAppPasswordProtectionOn) {
val confirmationTextId = if (config.appProtectionType == PROTECTION_FINGERPRINT) {
com.simplemobiletools.commons.R.string.fingerprint_setup_successfully
} else {
com.simplemobiletools.commons.R.string.protection_setup_successfully
}
ConfirmationDialog(this@SettingsActivity, "", confirmationTextId, com.simplemobiletools.commons.R.string.ok, 0) { }
}
}
}
}
}

View File

@ -5,9 +5,11 @@ import android.net.Uri
import android.os.Bundle
import com.simplemobiletools.commons.extensions.normalizePhoneNumber
import com.simplemobiletools.commons.extensions.sendEmailIntent
import com.simplemobiletools.commons.extensions.viewBinding
import com.simplemobiletools.commons.helpers.NavigationIcon
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.VCardViewerAdapter
import com.simplemobiletools.smsmessenger.databinding.ActivityVcardViewerBinding
import com.simplemobiletools.smsmessenger.extensions.dialNumber
import com.simplemobiletools.smsmessenger.helpers.EXTRA_VCARD_URI
import com.simplemobiletools.smsmessenger.helpers.parseVCardFromUri
@ -16,17 +18,18 @@ import com.simplemobiletools.smsmessenger.models.VCardWrapper
import ezvcard.VCard
import ezvcard.property.Email
import ezvcard.property.Telephone
import kotlinx.android.synthetic.main.activity_vcard_viewer.*
class VCardViewerActivity : SimpleActivity() {
private val binding by viewBinding(ActivityVcardViewerBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vcard_viewer)
setContentView(binding.root)
updateMaterialActivityViews(vcard_viewer_coordinator, contacts_list, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(contacts_list, vcard_toolbar)
updateMaterialActivityViews(binding.vcardViewerCoordinator, binding.contactsList, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(binding.contactsList, binding.vcardToolbar)
val vCardUri = intent.getParcelableExtra(EXTRA_VCARD_URI) as? Uri
if (vCardUri != null) {
@ -41,11 +44,11 @@ class VCardViewerActivity : SimpleActivity() {
override fun onResume() {
super.onResume()
setupToolbar(vcard_toolbar, NavigationIcon.Arrow)
setupToolbar(binding.vcardToolbar, NavigationIcon.Arrow)
}
private fun setupOptionsMenu(vCardUri: Uri) {
vcard_toolbar.setOnMenuItemClickListener { menuItem ->
binding.vcardToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.add_contact -> {
val intent = Intent(Intent.ACTION_VIEW).apply {
@ -55,6 +58,7 @@ class VCardViewerActivity : SimpleActivity() {
}
startActivity(intent)
}
else -> return@setOnMenuItemClickListener false
}
return@setOnMenuItemClickListener true
@ -69,7 +73,7 @@ class VCardViewerActivity : SimpleActivity() {
handleClick(item)
}
}
contacts_list.adapter = adapter
binding.contactsList.adapter = adapter
}
private fun handleClick(property: VCardPropertyWrapper) {

View File

@ -0,0 +1,96 @@
package com.simplemobiletools.smsmessenger.adapters
import android.view.Menu
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.deleteConversation
import com.simplemobiletools.smsmessenger.extensions.updateConversationArchivedStatus
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Conversation
class ArchivedConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) {
override fun getActionMenuId() = R.menu.cab_archived_conversations
override fun prepareActionMode(menu: Menu) {}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
when (id) {
R.id.cab_delete -> askConfirmDelete()
R.id.cab_unarchive -> unarchiveConversation()
R.id.cab_select_all -> selectAll()
}
}
private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = com.simplemobiletools.commons.R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
deleteConversations()
}
}
}
private fun deleteConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.threadId.hashCode())
}
removeConversationsFromList(conversationsToRemove)
}
private fun unarchiveConversation() {
if (selectedKeys.isEmpty()) {
return
}
ensureBackgroundThread {
val conversationsToUnarchive = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToUnarchive.forEach {
activity.updateConversationArchivedStatus(it.threadId, false)
}
removeConversationsFromList(conversationsToUnarchive)
}
}
private fun removeConversationsFromList(removedConversations: List<Conversation>) {
val newList = try {
currentList.toMutableList().apply { removeAll(removedConversations) }
} catch (ignored: Exception) {
currentList.toMutableList()
}
activity.runOnUiThread {
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
refreshMessages()
finishActMode()
} else {
submitList(newList)
if (newList.isEmpty()) {
refreshMessages()
}
}
}
}
}

View File

@ -2,11 +2,12 @@ package com.simplemobiletools.smsmessenger.adapters
import android.content.Intent
import android.graphics.drawable.Drawable
import android.view.View
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
@ -21,18 +22,19 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentDocumentPreviewBinding
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentMediaPreviewBinding
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentVcardPreviewBinding
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.AttachmentSelection
import kotlinx.android.synthetic.main.item_attachment_media_preview.view.*
import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
class AttachmentsAdapter(
val activity: BaseSimpleActivity,
val recyclerView: RecyclerView,
val onAttachmentsRemoved: () -> Unit,
val onReady: (() -> Unit)
) : ListAdapter<AttachmentSelection, AttachmentsAdapter.ViewHolder>(AttachmentDiffCallback()) {
) : ListAdapter<AttachmentSelection, AttachmentsAdapter.AttachmentsViewHolder>(AttachmentDiffCallback()) {
private val config = activity.config
private val resources = activity.resources
@ -45,37 +47,35 @@ class AttachmentsAdapter(
return getItem(position).viewType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (viewType) {
ATTACHMENT_DOCUMENT -> R.layout.item_attachment_document_preview
ATTACHMENT_VCARD -> R.layout.item_attachment_vcard_preview
ATTACHMENT_MEDIA -> R.layout.item_attachment_media_preview
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AttachmentsViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = when (viewType) {
ATTACHMENT_DOCUMENT -> ItemAttachmentDocumentPreviewBinding.inflate(inflater, parent, false)
ATTACHMENT_VCARD -> ItemAttachmentVcardPreviewBinding.inflate(inflater, parent, false)
ATTACHMENT_MEDIA -> ItemAttachmentMediaPreviewBinding.inflate(inflater, parent, false)
else -> throw IllegalArgumentException("Unknown view type: $viewType")
}
val view = activity.layoutInflater.inflate(layoutRes, parent, false)
return ViewHolder(view)
return AttachmentsViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
override fun onBindViewHolder(holder: AttachmentsViewHolder, position: Int) {
val attachment = getItem(position)
holder.bindView() { view, _ ->
holder.bindView { binding, _ ->
when (attachment.viewType) {
ATTACHMENT_DOCUMENT -> {
view.setupDocumentPreview(
(binding as ItemAttachmentDocumentPreviewBinding).setupDocumentPreview(
uri = attachment.uri,
title = attachment.filename,
mimeType = attachment.mimetype,
attachment = true,
onClick = { activity.launchViewIntent(attachment.uri, attachment.mimetype, attachment.filename) },
onRemoveButtonClicked = { removeAttachment(attachment) }
)
}
ATTACHMENT_VCARD -> {
view.setupVCardPreview(
(binding as ItemAttachmentVcardPreviewBinding).setupVCardPreview(
activity = activity,
uri = attachment.uri,
attachment = true,
onClick = {
val intent = Intent(activity, VCardViewerActivity::class.java).also {
it.putExtra(EXTRA_VCARD_URI, attachment.uri)
@ -85,7 +85,10 @@ class AttachmentsAdapter(
onRemoveButtonClicked = { removeAttachment(attachment) }
)
}
ATTACHMENT_MEDIA -> setupMediaPreview(view, attachment)
ATTACHMENT_MEDIA -> setupMediaPreview(
binding = binding as ItemAttachmentMediaPreviewBinding,
attachment = attachment
)
}
}
}
@ -113,13 +116,14 @@ class AttachmentsAdapter(
}
}
private fun setupMediaPreview(view: View, attachment: AttachmentSelection) {
view.apply {
media_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
media_attachment_holder.setOnClickListener {
private fun setupMediaPreview(binding: ItemAttachmentMediaPreviewBinding, attachment: AttachmentSelection) {
binding.apply {
mediaAttachmentHolder.background.applyColorFilter(primaryColor.darkenColor())
mediaAttachmentHolder.setOnClickListener {
activity.launchViewIntent(attachment.uri, attachment.mimetype, attachment.filename)
}
remove_attachment_button.apply {
removeAttachmentButtonHolder.removeAttachmentButton.apply {
beVisible()
background.applyColorFilter(primaryColor)
setOnClickListener {
@ -130,19 +134,21 @@ class AttachmentsAdapter(
val compressImage = attachment.mimetype.isImageMimeType() && !attachment.mimetype.isGifMimeType()
if (compressImage && attachment.isPending && config.mmsFileSizeLimit != FILE_SIZE_NONE) {
thumbnail.beGone()
compression_progress.beVisible()
compressionProgress.beVisible()
imageCompressor.compressImage(attachment.uri, config.mmsFileSizeLimit) { compressedUri ->
activity.runOnUiThread {
when (compressedUri) {
attachment.uri -> {
attachments.find { it.uri == attachment.uri }?.isPending = false
loadMediaPreview(view, attachment)
loadMediaPreview(this, attachment)
}
null -> {
activity.toast(R.string.compress_error)
removeAttachment(attachment)
}
else -> {
attachments.remove(attachment)
addAttachment(attachment.copy(uri = compressedUri, isPending = false))
@ -152,46 +158,44 @@ class AttachmentsAdapter(
}
}
} else {
loadMediaPreview(view, attachment)
loadMediaPreview(this, attachment)
}
}
}
private fun loadMediaPreview(view: View, attachment: AttachmentSelection) {
val roundedCornersRadius = resources.getDimension(R.dimen.activity_margin).toInt()
private fun loadMediaPreview(binding: ItemAttachmentMediaPreviewBinding, attachment: AttachmentSelection) {
val roundedCornersRadius = resources.getDimension(com.simplemobiletools.commons.R.dimen.activity_margin).toInt()
val size = resources.getDimension(R.dimen.attachment_preview_size).toInt()
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(CenterCrop(), RoundedCorners(roundedCornersRadius))
Glide.with(view.thumbnail)
Glide.with(binding.thumbnail)
.load(attachment.uri)
.transition(DrawableTransitionOptions.withCrossFade())
.override(size, size)
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
removeAttachment(attachment)
activity.toast(R.string.unknown_error_occurred)
activity.toast(com.simplemobiletools.commons.R.string.unknown_error_occurred)
return false
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean): Boolean {
view.thumbnail.beVisible()
view.play_icon.beVisibleIf(attachment.mimetype.isVideoMimeType())
view.compression_progress.beGone()
override fun onResourceReady(dr: Drawable, a: Any, t: Target<Drawable>, d: DataSource, i: Boolean): Boolean {
binding.thumbnail.beVisible()
binding.playIcon.beVisibleIf(attachment.mimetype.isVideoMimeType())
binding.compressionProgress.beGone()
return false
}
})
.into(view.thumbnail)
.into(binding.thumbnail)
}
open inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(callback: (itemView: View, adapterPosition: Int) -> Unit): View {
return itemView.apply {
callback(this, adapterPosition)
}
inner class AttachmentsViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindView(callback: (binding: ViewBinding, adapterPosition: Int) -> Unit) {
callback(binding, adapterPosition)
}
}
}

View File

@ -5,17 +5,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Filter
import android.widget.RelativeLayout
import android.widget.TextView
import com.simplemobiletools.commons.databinding.ItemContactWithNumberBinding
import com.simplemobiletools.commons.extensions.darkenColor
import com.simplemobiletools.commons.extensions.getContrastColor
import com.simplemobiletools.commons.extensions.getProperBackgroundColor
import com.simplemobiletools.commons.extensions.normalizeString
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.config
class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList<SimpleContact>) : ArrayAdapter<SimpleContact>(activity, 0, contacts) {
var resultList = ArrayList<SimpleContact>()
@ -24,27 +21,26 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar
val contact = resultList.getOrNull(position)
var listItem = convertView
if (listItem == null || listItem.tag != contact?.name?.isNotEmpty()) {
listItem = LayoutInflater.from(activity).inflate(R.layout.item_contact_with_number, parent, false)
listItem = ItemContactWithNumberBinding.inflate(LayoutInflater.from(activity), parent, false).root
}
listItem!!.apply {
tag = contact?.name?.isNotEmpty()
listItem.tag = contact?.name?.isNotEmpty()
ItemContactWithNumberBinding.bind(listItem).apply {
// clickable and focusable properties seem to break Autocomplete clicking, so remove them
findViewById<View>(R.id.item_contact_frame).apply {
itemContactFrame.apply {
isClickable = false
isFocusable = false
}
val backgroundColor = activity.getProperBackgroundColor()
findViewById<RelativeLayout>(R.id.item_contact_holder).setBackgroundColor(backgroundColor.darkenColor())
findViewById<TextView>(R.id.item_contact_name).setTextColor(backgroundColor.getContrastColor())
findViewById<TextView>(R.id.item_contact_number).setTextColor(backgroundColor.getContrastColor())
itemContactFrame.setBackgroundColor(backgroundColor.darkenColor())
itemContactName.setTextColor(backgroundColor.getContrastColor())
itemContactNumber.setTextColor(backgroundColor.getContrastColor())
if (contact != null) {
findViewById<TextView>(R.id.item_contact_name).text = contact.name
findViewById<TextView>(R.id.item_contact_number).text = contact.phoneNumbers.first().normalizedNumber
SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name)
itemContactName.text = contact.name
itemContactNumber.text = contact.phoneNumbers.first().normalizedNumber
SimpleContactsHelper(context).loadContactImage(contact.photoUri, itemContactImage, contact.name)
}
}
@ -55,24 +51,27 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (constraint != null) {
resultList.clear()
val results = mutableListOf<SimpleContact>()
val searchString = constraint.toString().normalizeString()
contacts.forEach {
if (it.doesContainPhoneNumber(searchString) || it.name.contains(searchString, true)) {
resultList.add(it)
results.add(it)
}
}
resultList.sortWith(compareBy { !it.name.startsWith(searchString, true) })
results.sortWith(compareBy { !it.name.startsWith(searchString, true) })
filterResults.values = resultList
filterResults.count = resultList.size
filterResults.values = results
filterResults.count = results.size
}
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
if (results?.count ?: -1 > 0) {
if (results != null && results.count > 0) {
resultList.clear()
@Suppress("UNCHECKED_CAST")
resultList.addAll(results.values as List<SimpleContact>)
notifyDataSetChanged()
} else {
notifyDataSetInvalidated()

View File

@ -0,0 +1,185 @@
package com.simplemobiletools.smsmessenger.adapters
import android.graphics.Typeface
import android.os.Parcelable
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.databinding.ItemConversationBinding
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.models.Conversation
@Suppress("LeakingThis")
abstract class BaseConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : MyRecyclerViewListAdapter<Conversation>(activity, recyclerView, ConversationDiffCallback(), itemClick, onRefresh),
RecyclerViewFastScroller.OnPopupTextUpdate {
private var fontSize = activity.getTextSize()
private var drafts = HashMap<Long, String?>()
private var recyclerViewState: Parcelable? = null
init {
setupDragListener(true)
ensureBackgroundThread {
fetchDrafts(drafts)
}
setHasStableIds(true)
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() = restoreRecyclerViewState()
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) = restoreRecyclerViewState()
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = restoreRecyclerViewState()
})
}
fun updateFontSize() {
fontSize = activity.getTextSize()
notifyDataSetChanged()
}
fun updateConversations(newConversations: ArrayList<Conversation>, commitCallback: (() -> Unit)? = null) {
saveRecyclerViewState()
submitList(newConversations.toList(), commitCallback)
}
fun updateDrafts() {
ensureBackgroundThread {
val newDrafts = HashMap<Long, String?>()
fetchDrafts(newDrafts)
if (drafts.hashCode() != newDrafts.hashCode()) {
drafts = newDrafts
activity.runOnUiThread {
notifyDataSetChanged()
}
}
}
}
override fun getSelectableItemCount() = itemCount
protected fun getSelectedItems() = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = currentList.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { it.hashCode() == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemConversationBinding.inflate(layoutInflater, parent, false)
return createViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val conversation = getItem(position)
holder.bindView(conversation, allowSingleClick = true, allowLongClick = true) { itemView, _ ->
setupView(itemView, conversation)
}
bindViewHolder(holder)
}
override fun getItemId(position: Int) = getItem(position).threadId
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing) {
val itemView = ItemConversationBinding.bind(holder.itemView)
Glide.with(activity).clear(itemView.conversationImage)
}
}
private fun fetchDrafts(drafts: HashMap<Long, String?>) {
drafts.clear()
for ((threadId, draft) in activity.getAllDrafts()) {
drafts[threadId] = draft
}
}
private fun setupView(view: View, conversation: Conversation) {
ItemConversationBinding.bind(view).apply {
root.setupViewBackground(activity)
val smsDraft = drafts[conversation.threadId]
draftIndicator.beVisibleIf(smsDraft != null)
draftIndicator.setTextColor(properPrimaryColor)
pinIndicator.beVisibleIf(activity.config.pinnedConversations.contains(conversation.threadId.toString()))
pinIndicator.applyColorFilter(textColor)
conversationFrame.isSelected = selectedKeys.contains(conversation.hashCode())
conversationAddress.apply {
text = conversation.title
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.2f)
}
conversationBodyShort.apply {
text = smsDraft ?: conversation.snippet
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.9f)
}
conversationDate.apply {
text = conversation.date.formatDateOrTime(context, true, false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
}
val style = if (conversation.read) {
conversationBodyShort.alpha = 0.7f
if (conversation.isScheduled) Typeface.ITALIC else Typeface.NORMAL
} else {
conversationBodyShort.alpha = 1f
if (conversation.isScheduled) Typeface.BOLD_ITALIC else Typeface.BOLD
}
conversationAddress.setTypeface(null, style)
conversationBodyShort.setTypeface(null, style)
arrayListOf(conversationAddress, conversationBodyShort, conversationDate).forEach {
it.setTextColor(textColor)
}
// at group conversations we use an icon as the placeholder, not any letter
val placeholder = if (conversation.isGroupConversation) {
SimpleContactsHelper(activity).getColoredGroupIcon(conversation.title)
} else {
null
}
SimpleContactsHelper(activity).loadContactImage(conversation.photoUri, conversationImage, conversation.title, placeholder)
}
}
override fun onChange(position: Int) = currentList.getOrNull(position)?.title ?: ""
private fun saveRecyclerViewState() {
recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
}
private fun restoreRecyclerViewState() {
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
}
private class ConversationDiffCallback : DiffUtil.ItemCallback<Conversation>() {
override fun areItemsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
return Conversation.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
return Conversation.areContentsTheSame(oldItem, newItem)
}
}
}

View File

@ -5,17 +5,14 @@ import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.databinding.ItemContactWithNumberBinding
import com.simplemobiletools.commons.extensions.getTextSize
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import java.util.*
class ContactsAdapter(
activity: SimpleActivity, var contacts: ArrayList<SimpleContact>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
@ -40,11 +37,14 @@ class ContactsAdapter(
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_contact_with_number, parent)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemContactWithNumberBinding.inflate(layoutInflater, parent, false)
return createViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contact = contacts[position]
holder.bindView(contact, true, false) { itemView, layoutPosition ->
holder.bindView(contact, allowSingleClick = true, allowLongClick = false) { itemView, _ ->
setupView(itemView, contact)
}
bindViewHolder(holder)
@ -62,27 +62,28 @@ class ContactsAdapter(
}
private fun setupView(view: View, contact: SimpleContact) {
view.apply {
findViewById<TextView>(R.id.item_contact_name).apply {
ItemContactWithNumberBinding.bind(view).apply {
itemContactName.apply {
text = contact.name
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.2f)
}
findViewById<TextView>(R.id.item_contact_number).apply {
itemContactNumber.apply {
text = TextUtils.join(", ", contact.phoneNumbers.map { it.normalizedNumber })
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
}
SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name)
SimpleContactsHelper(activity).loadContactImage(contact.photoUri, itemContactImage, contact.name)
}
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing) {
Glide.with(activity).clear(holder.itemView.findViewById<ImageView>(R.id.item_contact_image))
val binding = ItemContactWithNumberBinding.bind(holder.itemView)
Glide.with(activity).clear(binding.itemContactImage)
}
}
}

View File

@ -1,24 +1,12 @@
package com.simplemobiletools.smsmessenger.adapters
import android.content.Intent
import android.graphics.Typeface
import android.os.Parcelable
import android.text.TextUtils
import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.KEY_PHONE
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.views.MyRecyclerView
@ -29,31 +17,10 @@ import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
import com.simplemobiletools.smsmessenger.models.Conversation
import kotlinx.android.synthetic.main.item_conversation.view.*
class ConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : MyRecyclerViewListAdapter<Conversation>(activity, recyclerView, ConversationDiffCallback(), itemClick, onRefresh),
RecyclerViewFastScroller.OnPopupTextUpdate {
private var fontSize = activity.getTextSize()
private var drafts = HashMap<Long, String?>()
private var recyclerViewState: Parcelable? = null
init {
setupDragListener(true)
ensureBackgroundThread {
fetchDrafts(drafts)
}
setHasStableIds(true)
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() = restoreRecyclerViewState()
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) = restoreRecyclerViewState()
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = restoreRecyclerViewState()
})
}
) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) {
override fun getActionMenuId() = R.menu.cab_conversations
override fun prepareActionMode(menu: Menu) {
@ -61,9 +28,10 @@ class ConversationsAdapter(
val isSingleSelection = isOneItemSelected()
val selectedConversation = selectedItems.firstOrNull() ?: return
val isGroupConversation = selectedConversation.isGroupConversation
val archiveAvailable = activity.config.isArchiveAvailable
menu.apply {
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(R.string.block_number)
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(com.simplemobiletools.commons.R.string.block_number)
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
findItem(R.id.cab_add_number_to_contact).isVisible = isSingleSelection && !isGroupConversation
findItem(R.id.cab_dial_number).isVisible = isSingleSelection && !isGroupConversation && !isShortCodeWithLetters(selectedConversation.phoneNumber)
@ -71,6 +39,7 @@ class ConversationsAdapter(
findItem(R.id.cab_rename_conversation).isVisible = isSingleSelection && isGroupConversation
findItem(R.id.cab_mark_as_read).isVisible = selectedItems.any { !it.read }
findItem(R.id.cab_mark_as_unread).isVisible = selectedItems.any { it.read }
findItem(R.id.cab_archive).isVisible = archiveAvailable
checkPinBtnVisibility(this)
}
}
@ -86,6 +55,7 @@ class ConversationsAdapter(
R.id.cab_dial_number -> dialNumber()
R.id.cab_copy_number -> copyNumberToClipboard()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_archive -> askConfirmArchive()
R.id.cab_rename_conversation -> renameConversation(getSelectedItems().first())
R.id.cab_mark_as_read -> markAsRead()
R.id.cab_mark_as_unread -> markAsUnread()
@ -95,37 +65,6 @@ class ConversationsAdapter(
}
}
override fun getSelectableItemCount() = itemCount
override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = currentList.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { it.hashCode() == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_conversation, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val conversation = getItem(position)
holder.bindView(conversation, allowSingleClick = true, allowLongClick = true) { itemView, _ ->
setupView(itemView, conversation)
}
bindViewHolder(holder)
}
override fun getItemId(position: Int) = getItem(position).threadId
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing) {
Glide.with(activity).clear(holder.itemView.conversation_image)
}
}
private fun tryBlocking() {
if (activity.isOrWasThankYouInstalled()) {
askConfirmBlock()
@ -137,7 +76,7 @@ class ConversationsAdapter(
private fun askConfirmBlock() {
val numbers = getSelectedItems().distinctBy { it.phoneNumber }.map { it.phoneNumber }
val numbersString = TextUtils.join(", ", numbers)
val question = String.format(resources.getString(R.string.block_confirmation), numbersString)
val question = String.format(resources.getString(com.simplemobiletools.commons.R.string.block_confirmation), numbersString)
ConfirmationDialog(activity, question) {
blockNumbers()
@ -181,7 +120,7 @@ class ConversationsAdapter(
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = R.string.deletion_confirmation
val baseString = com.simplemobiletools.commons.R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
@ -191,6 +130,50 @@ class ConversationsAdapter(
}
}
private fun askConfirmArchive() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = R.string.archive_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
archiveConversations()
}
}
}
private fun archiveConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.updateConversationArchivedStatus(it.threadId, true)
activity.notificationManager.cancel(it.threadId.hashCode())
}
val newList = try {
currentList.toMutableList().apply { removeAll(conversationsToRemove) }
} catch (ignored: Exception) {
currentList.toMutableList()
}
activity.runOnUiThread {
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
refreshMessages()
finishActMode()
} else {
submitList(newList)
if (newList.isEmpty()) {
refreshMessages()
}
}
}
}
private fun deleteConversations() {
if (selectedKeys.isEmpty()) {
return
@ -199,7 +182,7 @@ class ConversationsAdapter(
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.hashCode())
activity.notificationManager.cancel(it.threadId.hashCode())
}
val newList = try {
@ -276,8 +259,6 @@ class ConversationsAdapter(
}
}
private fun getSelectedItems() = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
private fun pinConversation(pin: Boolean) {
val conversations = getSelectedItems()
if (conversations.isEmpty()) {
@ -303,112 +284,10 @@ class ConversationsAdapter(
menu.findItem(R.id.cab_unpin_conversation).isVisible = selectedConversations.any { pinnedConversations.contains(it.threadId.toString()) }
}
private fun fetchDrafts(drafts: HashMap<Long, String?>) {
drafts.clear()
for ((threadId, draft) in activity.getAllDrafts()) {
drafts[threadId] = draft
}
}
fun updateFontSize() {
fontSize = activity.getTextSize()
notifyDataSetChanged()
}
fun updateConversations(newConversations: ArrayList<Conversation>, commitCallback: (() -> Unit)? = null) {
saveRecyclerViewState()
submitList(newConversations.toList(), commitCallback)
}
fun updateDrafts() {
ensureBackgroundThread {
val newDrafts = HashMap<Long, String?>()
fetchDrafts(newDrafts)
if (drafts.hashCode() != newDrafts.hashCode()) {
drafts = newDrafts
activity.runOnUiThread {
notifyDataSetChanged()
}
}
}
}
private fun setupView(view: View, conversation: Conversation) {
view.apply {
val smsDraft = drafts[conversation.threadId]
draft_indicator.beVisibleIf(smsDraft != null)
draft_indicator.setTextColor(properPrimaryColor)
pin_indicator.beVisibleIf(activity.config.pinnedConversations.contains(conversation.threadId.toString()))
pin_indicator.applyColorFilter(textColor)
conversation_frame.isSelected = selectedKeys.contains(conversation.hashCode())
conversation_address.apply {
text = conversation.title
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.2f)
}
conversation_body_short.apply {
text = smsDraft ?: conversation.snippet
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.9f)
}
conversation_date.apply {
text = conversation.date.formatDateOrTime(context, true, false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
}
val style = if (conversation.read) {
conversation_body_short.alpha = 0.7f
if (conversation.isScheduled) Typeface.ITALIC else Typeface.NORMAL
} else {
conversation_body_short.alpha = 1f
if (conversation.isScheduled) Typeface.BOLD_ITALIC else Typeface.BOLD
}
conversation_address.setTypeface(null, style)
conversation_body_short.setTypeface(null, style)
arrayListOf<TextView>(conversation_address, conversation_body_short, conversation_date).forEach {
it.setTextColor(textColor)
}
// at group conversations we use an icon as the placeholder, not any letter
val placeholder = if (conversation.isGroupConversation) {
SimpleContactsHelper(context).getColoredGroupIcon(conversation.title)
} else {
null
}
SimpleContactsHelper(context).loadContactImage(conversation.photoUri, conversation_image, conversation.title, placeholder)
}
}
override fun onChange(position: Int) = currentList.getOrNull(position)?.title ?: ""
private fun refreshConversations() {
activity.runOnUiThread {
refreshMessages()
finishActMode()
}
}
private fun saveRecyclerViewState() {
recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
}
private fun restoreRecyclerViewState() {
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
}
private class ConversationDiffCallback : DiffUtil.ItemCallback<Conversation>() {
override fun areItemsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
return Conversation.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
return Conversation.areContentsTheSame(oldItem, newItem)
}
}
}

View File

@ -0,0 +1,108 @@
package com.simplemobiletools.smsmessenger.adapters
import android.view.Menu
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.deleteConversation
import com.simplemobiletools.smsmessenger.extensions.restoreAllMessagesFromRecycleBinForConversation
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Conversation
class RecycleBinConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) {
override fun getActionMenuId() = R.menu.cab_recycle_bin_conversations
override fun prepareActionMode(menu: Menu) {}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
when (id) {
R.id.cab_delete -> askConfirmDelete()
R.id.cab_restore -> askConfirmRestore()
R.id.cab_select_all -> selectAll()
}
}
private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = com.simplemobiletools.commons.R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
deleteConversations()
}
}
}
private fun deleteConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.threadId.hashCode())
}
removeConversationsFromList(conversationsToRemove)
}
private fun askConfirmRestore() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = R.string.restore_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
restoreConversations()
}
}
}
private fun restoreConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.restoreAllMessagesFromRecycleBinForConversation(it.threadId)
}
removeConversationsFromList(conversationsToRemove)
}
private fun removeConversationsFromList(removedConversations: List<Conversation>) {
val newList = try {
currentList.toMutableList().apply { removeAll(removedConversations) }
} catch (ignored: Exception) {
currentList.toMutableList()
}
activity.runOnUiThread {
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
refreshMessages()
finishActMode()
} else {
submitList(newList)
if (newList.isEmpty()) {
refreshMessages()
}
}
}
}
}

View File

@ -10,10 +10,9 @@ import com.simplemobiletools.commons.extensions.getTextSize
import com.simplemobiletools.commons.extensions.highlightTextPart
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.databinding.ItemSearchResultBinding
import com.simplemobiletools.smsmessenger.models.SearchResult
import kotlinx.android.synthetic.main.item_search_result.view.*
class SearchResultsAdapter(
activity: SimpleActivity, var searchResults: ArrayList<SearchResult>, recyclerView: MyRecyclerView, highlightText: String, itemClick: (Any) -> Unit
@ -40,11 +39,14 @@ class SearchResultsAdapter(
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_search_result, parent)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemSearchResultBinding.inflate(layoutInflater, parent, false)
return createViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val searchResult = searchResults[position]
holder.bindView(searchResult, true, false) { itemView, layoutPosition ->
holder.bindView(searchResult, allowSingleClick = true, allowLongClick = false) { itemView, _ ->
setupView(itemView, searchResult)
}
bindViewHolder(holder)
@ -64,33 +66,34 @@ class SearchResultsAdapter(
}
private fun setupView(view: View, searchResult: SearchResult) {
view.apply {
search_result_title.apply {
ItemSearchResultBinding.bind(view).apply {
searchResultTitle.apply {
text = searchResult.title.highlightTextPart(textToHighlight, properPrimaryColor)
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.2f)
}
search_result_snippet.apply {
searchResultSnippet.apply {
text = searchResult.snippet.highlightTextPart(textToHighlight, properPrimaryColor)
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.9f)
}
search_result_date.apply {
searchResultDate.apply {
text = searchResult.date
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
}
SimpleContactsHelper(context).loadContactImage(searchResult.photoUri, search_result_image, searchResult.title)
SimpleContactsHelper(activity).loadContactImage(searchResult.photoUri, searchResultImage, searchResult.title)
}
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.search_result_image != null) {
Glide.with(activity).clear(holder.itemView.search_result_image)
if (!activity.isDestroyed && !activity.isFinishing) {
val binding = ItemSearchResultBinding.bind(holder.itemView)
Glide.with(activity).clear(binding.searchResultImage)
}
}
}

View File

@ -12,7 +12,13 @@ import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
@ -33,6 +39,9 @@ import com.simplemobiletools.smsmessenger.activities.NewConversationActivity
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity
import com.simplemobiletools.smsmessenger.databinding.*
import com.simplemobiletools.smsmessenger.dialogs.DeleteConfirmationDialog
import com.simplemobiletools.smsmessenger.dialogs.MessageDetailsDialog
import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
@ -40,24 +49,13 @@ import com.simplemobiletools.smsmessenger.models.Attachment
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.models.ThreadItem
import com.simplemobiletools.smsmessenger.models.ThreadItem.*
import kotlinx.android.synthetic.main.item_attachment_image.view.*
import kotlinx.android.synthetic.main.item_received_message.view.*
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_play_outline
import kotlinx.android.synthetic.main.item_sent_message.view.*
import kotlinx.android.synthetic.main.item_thread_date_time.view.*
import kotlinx.android.synthetic.main.item_thread_error.view.*
import kotlinx.android.synthetic.main.item_thread_loading.view.*
import kotlinx.android.synthetic.main.item_thread_sending.view.*
import kotlinx.android.synthetic.main.item_thread_success.view.*
class ThreadAdapter(
activity: SimpleActivity,
recyclerView: MyRecyclerView,
itemClick: (Any) -> Unit,
val deleteMessages: (messages: List<Message>) -> Unit
val isRecycleBin: Boolean,
val deleteMessages: (messages: List<Message>, toRecycleBin: Boolean, fromRecycleBin: Boolean) -> Unit
) : MyRecyclerViewListAdapter<ThreadItem>(activity, recyclerView, ThreadItemDiffCallback(), itemClick) {
private var fontSize = activity.getTextSize()
@ -82,6 +80,8 @@ class ThreadAdapter(
findItem(R.id.cab_share).isVisible = isOneItemSelected && hasText
findItem(R.id.cab_forward_message).isVisible = isOneItemSelected
findItem(R.id.cab_select_text).isVisible = isOneItemSelected && hasText
findItem(R.id.cab_properties).isVisible = isOneItemSelected
findItem(R.id.cab_restore).isVisible = isRecycleBin
}
}
@ -97,7 +97,9 @@ class ThreadAdapter(
R.id.cab_forward_message -> forwardMessage()
R.id.cab_select_text -> selectText()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_restore -> askConfirmRestore()
R.id.cab_select_all -> selectAll()
R.id.cab_properties -> showMessageDetails()
}
}
@ -114,16 +116,16 @@ class ThreadAdapter(
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = when (viewType) {
THREAD_LOADING -> R.layout.item_thread_loading
THREAD_DATE_TIME -> R.layout.item_thread_date_time
THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message
THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error
THREAD_SENT_MESSAGE_SENT -> R.layout.item_thread_success
THREAD_SENT_MESSAGE_SENDING -> R.layout.item_thread_sending
else -> R.layout.item_sent_message
val binding = when (viewType) {
THREAD_LOADING -> ItemThreadLoadingBinding.inflate(layoutInflater, parent, false)
THREAD_DATE_TIME -> ItemThreadDateTimeBinding.inflate(layoutInflater, parent, false)
THREAD_SENT_MESSAGE_ERROR -> ItemThreadErrorBinding.inflate(layoutInflater, parent, false)
THREAD_SENT_MESSAGE_SENT -> ItemThreadSuccessBinding.inflate(layoutInflater, parent, false)
THREAD_SENT_MESSAGE_SENDING -> ItemThreadSendingBinding.inflate(layoutInflater, parent, false)
else -> ItemMessageBinding.inflate(layoutInflater, parent, false)
}
return createViewHolder(layout, parent)
return ThreadViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -184,6 +186,11 @@ class ThreadAdapter(
}
}
private fun showMessageDetails() {
val message = getSelectedItems().firstOrNull() as? Message ?: return
MessageDetailsDialog(activity, message)
}
private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size
@ -195,14 +202,43 @@ class ThreadAdapter(
return
}
val baseString = R.string.deletion_confirmation
val baseString = if (activity.config.useRecycleBin && !isRecycleBin) {
com.simplemobiletools.commons.R.string.move_to_recycle_bin_confirmation
} else {
com.simplemobiletools.commons.R.string.deletion_confirmation
}
val question = String.format(resources.getString(baseString), items)
DeleteConfirmationDialog(activity, question, activity.config.useRecycleBin && !isRecycleBin) { skipRecycleBin ->
ensureBackgroundThread {
val messagesToRemove = getSelectedItems()
if (messagesToRemove.isNotEmpty()) {
val toRecycleBin = !skipRecycleBin && activity.config.useRecycleBin && !isRecycleBin
deleteMessages(messagesToRemove.filterIsInstance<Message>(), toRecycleBin, false)
}
}
}
}
private fun askConfirmRestore() {
val itemsCnt = selectedKeys.size
// not sure how we can get UnknownFormatConversionException here, so show the error and hope that someone reports it
val items = try {
resources.getQuantityString(R.plurals.delete_messages, itemsCnt, itemsCnt)
} catch (e: Exception) {
activity.showErrorToast(e)
return
}
val baseString = R.string.restore_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
val messagesToRemove = getSelectedItems()
if (messagesToRemove.isNotEmpty()) {
deleteMessages(messagesToRemove.filterIsInstance<Message>())
val messagesToRestore = getSelectedItems()
if (messagesToRestore.isNotEmpty()) {
deleteMessages(messagesToRestore.filterIsInstance<Message>(), false, true)
}
}
}
@ -237,258 +273,289 @@ class ThreadAdapter(
}
private fun setupView(holder: ViewHolder, view: View, message: Message) {
view.apply {
thread_message_holder.isSelected = selectedKeys.contains(message.hashCode())
thread_message_body.apply {
ItemMessageBinding.bind(view).apply {
threadMessageHolder.isSelected = selectedKeys.contains(message.hashCode())
threadMessageBody.apply {
text = message.body
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
beVisibleIf(message.body.isNotEmpty())
setOnLongClickListener {
holder.viewLongClicked()
true
}
setOnClickListener {
holder.viewClicked(message)
}
}
thread_message_body.beVisibleIf(message.body.isNotEmpty())
if (message.isReceivedMessage()) {
setupReceivedMessageView(view, message)
setupReceivedMessageView(messageBinding = this, message = message)
} else {
setupSentMessageView(view, message)
}
thread_message_body.setOnLongClickListener {
holder.viewLongClicked()
true
}
thread_message_body.setOnClickListener {
holder.viewClicked(message)
setupSentMessageView(messageBinding = this, message = message)
}
if (message.attachment?.attachments?.isNotEmpty() == true) {
thread_mesage_attachments_holder.beVisible()
thread_mesage_attachments_holder.removeAllViews()
threadMessageAttachmentsHolder.beVisible()
threadMessageAttachmentsHolder.removeAllViews()
for (attachment in message.attachment.attachments) {
val mimetype = attachment.mimetype
when {
mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> setupImageView(holder, view, message, attachment)
mimetype.isVCardMimeType() -> setupVCardView(holder, view, message, attachment)
else -> setupFileView(holder, view, message, attachment)
mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> setupImageView(holder, binding = this, message, attachment)
mimetype.isVCardMimeType() -> setupVCardView(holder, threadMessageAttachmentsHolder, message, attachment)
else -> setupFileView(holder, threadMessageAttachmentsHolder, message, attachment)
}
thread_message_play_outline.beVisibleIf(mimetype.startsWith("video/"))
threadMessagePlayOutline.beVisibleIf(mimetype.startsWith("video/"))
}
} else {
thread_mesage_attachments_holder.beGone()
threadMessageAttachmentsHolder.beGone()
threadMessagePlayOutline.beGone()
}
}
}
private fun setupReceivedMessageView(view: View, message: Message) {
view.apply {
thread_message_sender_photo.beVisible()
thread_message_sender_photo.setOnClickListener {
val contact = message.participants.first()
context.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) {
private fun setupReceivedMessageView(messageBinding: ItemMessageBinding, message: Message) {
messageBinding.apply {
with(ConstraintSet()) {
clone(threadMessageHolder)
clear(threadMessageWrapper.id, ConstraintSet.END)
connect(threadMessageWrapper.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
applyTo(threadMessageHolder)
}
threadMessageSenderPhoto.beVisible()
threadMessageSenderPhoto.setOnClickListener {
val contact = message.getSender()!!
activity.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) {
if (it != null) {
activity.startContactDetailsIntent(it)
}
}
}
thread_message_body.setTextColor(textColor)
thread_message_body.setLinkTextColor(context.getProperPrimaryColor())
threadMessageBody.apply {
background = AppCompatResources.getDrawable(activity, R.drawable.item_received_background)
setTextColor(textColor)
setLinkTextColor(activity.getProperPrimaryColor())
}
if (!activity.isFinishing && !activity.isDestroyed) {
val contactLetterIcon = SimpleContactsHelper(context).getContactLetterIcon(message.senderName)
val placeholder = BitmapDrawable(context.resources, contactLetterIcon)
val contactLetterIcon = SimpleContactsHelper(activity).getContactLetterIcon(message.senderName)
val placeholder = BitmapDrawable(activity.resources, contactLetterIcon)
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.error(placeholder)
.centerCrop()
Glide.with(context)
Glide.with(activity)
.load(message.senderPhotoUri)
.placeholder(placeholder)
.apply(options)
.apply(RequestOptions.circleCropTransform())
.into(thread_message_sender_photo)
.into(threadMessageSenderPhoto)
}
}
}
private fun setupSentMessageView(view: View, message: Message) {
view.apply {
thread_message_sender_photo?.beGone()
val background = context.getProperPrimaryColor()
thread_message_body.background.applyColorFilter(background)
val contrastColor = background.getContrastColor()
thread_message_body.setTextColor(contrastColor)
thread_message_body.setLinkTextColor(contrastColor)
val padding = thread_message_body.paddingStart
if (message.isScheduled) {
thread_message_scheduled_icon.beVisible()
thread_message_scheduled_icon.applyColorFilter(contrastColor)
val iconWidth = resources.getDimensionPixelSize(R.dimen.small_icon_size)
val rightPadding = padding + iconWidth
thread_message_body.setPadding(padding, padding, rightPadding, padding)
thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
} else {
thread_message_scheduled_icon.beGone()
thread_message_body.setPadding(padding, padding, padding, padding)
thread_message_body.typeface = Typeface.DEFAULT
private fun setupSentMessageView(messageBinding: ItemMessageBinding, message: Message) {
messageBinding.apply {
with(ConstraintSet()) {
clone(threadMessageHolder)
clear(threadMessageWrapper.id, ConstraintSet.START)
connect(threadMessageWrapper.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
applyTo(threadMessageHolder)
}
}
}
private fun setupImageView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) {
val mimetype = attachment.mimetype
val uri = attachment.getUri()
parent.apply {
val imageView = layoutInflater.inflate(R.layout.item_attachment_image, null)
thread_mesage_attachments_holder.addView(imageView)
val primaryColor = activity.getProperPrimaryColor()
val contrastColor = primaryColor.getContrastColor()
val placeholderDrawable = ColorDrawable(Color.TRANSPARENT)
val isTallImage = attachment.height > attachment.width
val transformation = if (isTallImage) CenterCrop() else FitCenter()
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.placeholder(placeholderDrawable)
.transform(transformation)
threadMessageBody.apply {
updateLayoutParams<RelativeLayout.LayoutParams> {
removeRule(RelativeLayout.END_OF)
addRule(RelativeLayout.ALIGN_PARENT_END)
}
var builder = Glide.with(context)
.load(uri)
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
thread_message_play_outline.beGone()
thread_mesage_attachments_holder.removeView(imageView)
return false
background = AppCompatResources.getDrawable(activity, R.drawable.item_sent_background)
background.applyColorFilter(primaryColor)
setTextColor(contrastColor)
setLinkTextColor(contrastColor)
if (message.isScheduled) {
typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
val scheduledDrawable = AppCompatResources.getDrawable(activity, com.simplemobiletools.commons.R.drawable.ic_clock_vector)?.apply {
applyColorFilter(contrastColor)
val size = lineHeight
setBounds(0, 0, size, size)
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean) = false
})
// limit attachment sizes to avoid causing OOM
var wantedAttachmentSize = Size(attachment.width, attachment.height)
if (wantedAttachmentSize.width > maxChatBubbleWidth) {
val newHeight = wantedAttachmentSize.height / (wantedAttachmentSize.width / maxChatBubbleWidth)
wantedAttachmentSize = Size(maxChatBubbleWidth.toInt(), newHeight.toInt())
}
builder = if (isTallImage) {
builder.override(wantedAttachmentSize.width, wantedAttachmentSize.width)
} else {
builder.override(wantedAttachmentSize.width, wantedAttachmentSize.height)
}
try {
builder.into(imageView.attachment_image)
} catch (ignore: Exception) {
}
imageView.attachment_image.setOnClickListener {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
setCompoundDrawables(null, null, scheduledDrawable, null)
} else {
activity.launchViewIntent(uri, mimetype, attachment.filename)
typeface = Typeface.DEFAULT
setCompoundDrawables(null, null, null, null)
}
}
imageView.setOnLongClickListener {
holder.viewLongClicked()
true
}
}
}
private fun setupVCardView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) {
val uri = attachment.getUri()
parent.apply {
val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply {
setupVCardPreview(
activity = activity,
uri = uri,
onClick = {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
val intent = Intent(context, VCardViewerActivity::class.java).also {
it.putExtra(EXTRA_VCARD_URI, uri)
}
context.startActivity(intent)
}
},
onLongClick = { holder.viewLongClicked() }
)
}
thread_mesage_attachments_holder.addView(vCardView)
}
}
private fun setupFileView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) {
private fun setupImageView(holder: ViewHolder, binding: ItemMessageBinding, message: Message, attachment: Attachment) = binding.apply {
val mimetype = attachment.mimetype
val uri = attachment.getUri()
parent.apply {
val attachmentView = layoutInflater.inflate(R.layout.item_attachment_document, null).apply {
setupDocumentPreview(
uri = uri,
title = attachment.filename,
mimeType = attachment.mimetype,
onClick = {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
activity.launchViewIntent(uri, mimetype, attachment.filename)
}
},
onLongClick = { holder.viewLongClicked() },
)
}
thread_mesage_attachments_holder.addView(attachmentView)
val imageView = ItemAttachmentImageBinding.inflate(layoutInflater)
threadMessageAttachmentsHolder.addView(imageView.root)
val placeholderDrawable = ColorDrawable(Color.TRANSPARENT)
val isTallImage = attachment.height > attachment.width
val transformation = if (isTallImage) CenterCrop() else FitCenter()
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.placeholder(placeholderDrawable)
.transform(transformation)
var builder = Glide.with(root.context)
.load(uri)
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
threadMessagePlayOutline.beGone()
threadMessageAttachmentsHolder.removeView(imageView.root)
return false
}
override fun onResourceReady(dr: Drawable, a: Any, t: Target<Drawable>, d: DataSource, i: Boolean) = false
})
// limit attachment sizes to avoid causing OOM
var wantedAttachmentSize = Size(attachment.width, attachment.height)
if (wantedAttachmentSize.width > maxChatBubbleWidth) {
val newHeight = wantedAttachmentSize.height / (wantedAttachmentSize.width / maxChatBubbleWidth)
wantedAttachmentSize = Size(maxChatBubbleWidth.toInt(), newHeight.toInt())
}
builder = if (isTallImage) {
builder.override(wantedAttachmentSize.width, wantedAttachmentSize.width)
} else {
builder.override(wantedAttachmentSize.width, wantedAttachmentSize.height)
}
try {
builder.into(imageView.attachmentImage)
} catch (ignore: Exception) {
}
imageView.attachmentImage.setOnClickListener {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
activity.launchViewIntent(uri, mimetype, attachment.filename)
}
}
imageView.root.setOnLongClickListener {
holder.viewLongClicked()
true
}
}
private fun setupVCardView(holder: ViewHolder, parent: LinearLayout, message: Message, attachment: Attachment) {
val uri = attachment.getUri()
val vCardView = ItemAttachmentVcardBinding.inflate(layoutInflater).apply {
setupVCardPreview(
activity = activity,
uri = uri,
onClick = {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
val intent = Intent(activity, VCardViewerActivity::class.java).also {
it.putExtra(EXTRA_VCARD_URI, uri)
}
activity.startActivity(intent)
}
},
onLongClick = { holder.viewLongClicked() }
)
}.root
parent.addView(vCardView)
}
private fun setupFileView(holder: ViewHolder, parent: LinearLayout, message: Message, attachment: Attachment) {
val mimetype = attachment.mimetype
val uri = attachment.getUri()
val attachmentView = ItemAttachmentDocumentBinding.inflate(layoutInflater).apply {
setupDocumentPreview(
uri = uri,
title = attachment.filename,
mimeType = attachment.mimetype,
onClick = {
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
activity.launchViewIntent(uri, mimetype, attachment.filename)
}
},
onLongClick = { holder.viewLongClicked() }
)
}.root
parent.addView(attachmentView)
}
private fun setupDateTime(view: View, dateTime: ThreadDateTime) {
view.apply {
thread_date_time.apply {
text = dateTime.date.formatDateOrTime(context, false, false)
ItemThreadDateTimeBinding.bind(view).apply {
threadDateTime.apply {
text = dateTime.date.formatDateOrTime(context, hideTimeAtOtherDays = false, showYearEvenIfCurrent = false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
}
thread_date_time.setTextColor(textColor)
threadDateTime.setTextColor(textColor)
thread_sim_icon.beVisibleIf(hasMultipleSIMCards)
thread_sim_number.beVisibleIf(hasMultipleSIMCards)
threadSimIcon.beVisibleIf(hasMultipleSIMCards)
threadSimNumber.beVisibleIf(hasMultipleSIMCards)
if (hasMultipleSIMCards) {
thread_sim_number.text = dateTime.simID
thread_sim_number.setTextColor(textColor.getContrastColor())
thread_sim_icon.applyColorFilter(textColor)
threadSimNumber.text = dateTime.simID
threadSimNumber.setTextColor(textColor.getContrastColor())
threadSimIcon.applyColorFilter(textColor)
}
}
}
private fun setupThreadSuccess(view: View, isDelivered: Boolean) {
view.thread_success.setImageResource(if (isDelivered) R.drawable.ic_check_double_vector else R.drawable.ic_check_vector)
view.thread_success.applyColorFilter(textColor)
ItemThreadSuccessBinding.bind(view).apply {
threadSuccess.setImageResource(if (isDelivered) R.drawable.ic_check_double_vector else com.simplemobiletools.commons.R.drawable.ic_check_vector)
threadSuccess.applyColorFilter(textColor)
}
}
private fun setupThreadError(view: View) {
view.thread_error.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize - 4)
val binding = ItemThreadErrorBinding.bind(view)
binding.threadError.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize - 4)
}
private fun setupThreadSending(view: View) {
view.thread_sending.apply {
ItemThreadSendingBinding.bind(view).threadSending.apply {
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
setTextColor(textColor)
}
}
private fun setupThreadLoading(view: View) = view.thread_loading.setIndicatorColor(properPrimaryColor)
private fun setupThreadLoading(view: View) {
val binding = ItemThreadLoadingBinding.bind(view)
binding.threadLoading.setIndicatorColor(properPrimaryColor)
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) {
Glide.with(activity).clear(holder.itemView.thread_message_sender_photo)
if (!activity.isDestroyed && !activity.isFinishing) {
val binding = (holder as ThreadViewHolder).binding
if (binding is ItemMessageBinding) {
Glide.with(activity).clear(binding.threadMessageSenderPhoto)
}
}
}
inner class ThreadViewHolder(val binding: ViewBinding) : ViewHolder(binding.root)
}
private class ThreadItemDiffCallback : DiffUtil.ItemCallback<ThreadItem>() {

View File

@ -1,7 +1,6 @@
package com.simplemobiletools.smsmessenger.adapters
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.drawable.toDrawable
import androidx.recyclerview.widget.RecyclerView
@ -14,14 +13,17 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.databinding.ItemVcardContactBinding
import com.simplemobiletools.smsmessenger.databinding.ItemVcardContactPropertyBinding
import com.simplemobiletools.smsmessenger.models.VCardPropertyWrapper
import com.simplemobiletools.smsmessenger.models.VCardWrapper
import kotlinx.android.synthetic.main.item_vcard_contact.view.*
import kotlinx.android.synthetic.main.item_vcard_contact_property.view.*
private const val TYPE_VCARD_CONTACT = 1
private const val TYPE_VCARD_CONTACT_PROPERTY = 2
class VCardViewerAdapter(
activity: SimpleActivity, private var items: MutableList<Any>, private val itemClick: (Any) -> Unit
) : RecyclerView.Adapter<VCardViewerAdapter.VCardViewHolder>() {
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var fontSize = activity.getTextSize()
private var textColor = activity.getProperTextColor()
@ -31,123 +33,129 @@ class VCardViewerAdapter(
override fun getItemViewType(position: Int): Int {
return when (val item = items[position]) {
is VCardWrapper -> R.layout.item_vcard_contact
is VCardPropertyWrapper -> R.layout.item_vcard_contact_property
is VCardWrapper -> TYPE_VCARD_CONTACT
is VCardPropertyWrapper -> TYPE_VCARD_CONTACT_PROPERTY
else -> throw IllegalArgumentException("Unexpected type: ${item::class.simpleName}")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VCardViewHolder {
val view = layoutInflater.inflate(viewType, parent, false)
return VCardViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_VCARD_CONTACT -> VCardContactViewHolder(
binding = ItemVcardContactBinding.inflate(layoutInflater, parent, false)
)
TYPE_VCARD_CONTACT_PROPERTY -> VCardPropertyViewHolder(
binding = ItemVcardContactPropertyBinding.inflate(layoutInflater, parent, false)
)
else -> throw IllegalArgumentException("Unexpected type: $viewType")
}
}
override fun onBindViewHolder(holder: VCardViewerAdapter.VCardViewHolder, position: Int) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
val itemView = holder.bindView()
when (item) {
is VCardWrapper -> setupVCardView(itemView, item)
is VCardPropertyWrapper -> setupVCardPropertyView(itemView, item)
else -> throw IllegalArgumentException("Unexpected type: ${item::class.simpleName}")
when (holder) {
is VCardContactViewHolder -> holder.bindView(item as VCardWrapper)
is VCardPropertyViewHolder -> holder.bindView(item as VCardPropertyWrapper)
}
}
private fun setupVCardView(view: View, item: VCardWrapper) {
val name = item.fullName
view.apply {
item_contact_name.apply {
text = name
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.1f)
}
item_contact_image.apply {
val photo = item.vCard.photos.firstOrNull()
val placeholder = if (name != null) {
SimpleContactsHelper(context).getContactLetterIcon(name).toDrawable(resources)
} else {
null
inner class VCardContactViewHolder(val binding: ItemVcardContactBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindView(item: VCardWrapper) {
val name = item.fullName
binding.apply {
itemContactName.apply {
text = name
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.1f)
}
val roundingRadius = resources.getDimensionPixelSize(R.dimen.big_margin)
val transformation = RoundedCorners(roundingRadius)
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.placeholder(placeholder)
.transform(transformation)
Glide.with(this)
.load(photo?.data ?: photo?.url)
.apply(options)
.transition(DrawableTransitionOptions.withCrossFade())
.into(this)
}
expand_collapse_icon.apply {
val expandCollapseDrawable = if (item.expanded) {
R.drawable.ic_collapse_up
} else {
R.drawable.ic_expand_down
}
setImageResource(expandCollapseDrawable)
applyColorFilter(textColor)
}
itemContactImage.apply {
val photo = item.vCard.photos.firstOrNull()
val placeholder = if (name != null) {
SimpleContactsHelper(context).getContactLetterIcon(name).toDrawable(resources)
} else {
null
}
if (items.size > 1) {
setOnClickListener {
expandOrCollapseRow(view, item)
val roundingRadius = resources.getDimensionPixelSize(com.simplemobiletools.commons.R.dimen.big_margin)
val transformation = RoundedCorners(roundingRadius)
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.placeholder(placeholder)
.transform(transformation)
Glide.with(this)
.load(photo?.data ?: photo?.url)
.apply(options)
.transition(DrawableTransitionOptions.withCrossFade())
.into(this)
}
}
onGlobalLayout {
if (items.size == 1) {
expandOrCollapseRow(view, item)
view.expand_collapse_icon.beGone()
expandCollapseIcon.apply {
val expandCollapseDrawable = if (item.expanded) {
R.drawable.ic_collapse_up
} else {
R.drawable.ic_expand_down
}
setImageResource(expandCollapseDrawable)
applyColorFilter(textColor)
}
if (items.size > 1) {
root.setOnClickListener {
expandOrCollapseRow(item)
}
}
root.onGlobalLayout {
if (items.size == 1) {
expandOrCollapseRow(item)
expandCollapseIcon.beGone()
}
}
}
}
}
private fun setupVCardPropertyView(view: View, property: VCardPropertyWrapper) {
view.apply {
item_vcard_property_title.apply {
text = property.value
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.1f)
private fun expandOrCollapseRow(item: VCardWrapper) {
val properties = item.properties
if (item.expanded) {
collapseRow(properties, item)
} else {
expandRow(properties, item)
}
item_vcard_property_subtitle.apply {
text = property.type
setTextColor(textColor)
}
view.setOnClickListener {
itemClick(property)
}
private fun expandRow(properties: List<VCardPropertyWrapper>, vCardWrapper: VCardWrapper) {
vCardWrapper.expanded = true
val nextPosition = items.indexOf(vCardWrapper) + 1
items.addAll(nextPosition, properties)
notifyItemRangeInserted(nextPosition, properties.size)
binding.expandCollapseIcon.setImageResource(R.drawable.ic_collapse_up)
}
private fun collapseRow(properties: List<VCardPropertyWrapper>, vCardWrapper: VCardWrapper) {
vCardWrapper.expanded = false
val nextPosition = items.indexOf(vCardWrapper) + 1
repeat(properties.size) {
items.removeAt(nextPosition)
}
notifyItemRangeRemoved(nextPosition, properties.size)
binding.expandCollapseIcon.setImageResource(R.drawable.ic_expand_down)
}
}
private fun expandOrCollapseRow(view: View, item: VCardWrapper) {
val properties = item.properties
if (item.expanded) {
collapseRow(view, properties, item)
} else {
expandRow(view, properties, item)
inner class VCardPropertyViewHolder(val binding: ItemVcardContactPropertyBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindView(item: VCardPropertyWrapper) {
binding.apply {
itemVcardPropertyTitle.apply {
text = item.value
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.1f)
}
itemVcardPropertySubtitle.apply {
text = item.type
setTextColor(textColor)
}
root.setOnClickListener {
itemClick(item)
}
}
}
}
private fun expandRow(view: View, properties: List<VCardPropertyWrapper>, vCardWrapper: VCardWrapper) {
vCardWrapper.expanded = true
val nextPosition = items.indexOf(vCardWrapper) + 1
items.addAll(nextPosition, properties)
notifyItemRangeInserted(nextPosition, properties.size)
view.expand_collapse_icon.setImageResource(R.drawable.ic_collapse_up)
}
private fun collapseRow(view: View, properties: List<VCardPropertyWrapper>, vCardWrapper: VCardWrapper) {
vCardWrapper.expanded = false
val nextPosition = items.indexOf(vCardWrapper) + 1
repeat(properties.size) {
items.removeAt(nextPosition)
}
notifyItemRangeRemoved(nextPosition, properties.size)
view.expand_collapse_icon.setImageResource(R.drawable.ic_expand_down)
}
inner class VCardViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView() = itemView
}
}

View File

@ -12,12 +12,9 @@ import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
import com.simplemobiletools.smsmessenger.models.Attachment
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.models.MessageAttachment
import com.simplemobiletools.smsmessenger.models.*
@Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 6)
@Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class, RecycleBinMessage::class], version = 8)
@TypeConverters(Converters::class)
abstract class MessagesDatabase : RoomDatabase() {
@ -43,6 +40,8 @@ abstract class MessagesDatabase : RoomDatabase() {
.addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_5)
.addMigrations(MIGRATION_5_6)
.addMigrations(MIGRATION_6_7)
.addMigrations(MIGRATION_7_8)
.build()
}
}
@ -106,5 +105,23 @@ abstract class MessagesDatabase : RoomDatabase() {
}
}
}
private val MIGRATION_6_7 = object : Migration(6, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("ALTER TABLE messages ADD COLUMN sender_phone_number TEXT NOT NULL DEFAULT ''")
}
}
}
private val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("ALTER TABLE conversations ADD COLUMN archived INTEGER NOT NULL DEFAULT 0")
execSQL("CREATE TABLE IF NOT EXISTS `recycle_bin_messages` (`id` INTEGER NOT NULL PRIMARY KEY, `deleted_ts` INTEGER NOT NULL)")
execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recycle_bin_messages_id` ON `recycle_bin_messages` (`id`)")
}
}
}
}
}

View File

@ -0,0 +1,42 @@
package com.simplemobiletools.smsmessenger.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.smsmessenger.databinding.DialogAddBlockedKeywordBinding
import com.simplemobiletools.smsmessenger.extensions.config
class AddBlockedKeywordDialog(val activity: BaseSimpleActivity, private val originalKeyword: String? = null, val callback: () -> Unit) {
init {
val binding = DialogAddBlockedKeywordBinding.inflate(activity.layoutInflater).apply {
if (originalKeyword != null) {
addBlockedKeywordEdittext.setText(originalKeyword)
}
}
activity.getAlertDialogBuilder()
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(binding.root, this) { alertDialog ->
alertDialog.showKeyboard(binding.addBlockedKeywordEdittext)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val newBlockedKeyword = binding.addBlockedKeywordEdittext.value
if (originalKeyword != null && newBlockedKeyword != originalKeyword) {
activity.config.removeBlockedKeyword(originalKeyword)
}
if (newBlockedKeyword.isNotEmpty()) {
activity.config.addBlockedKeyword(newBlockedKeyword)
}
callback()
alertDialog.dismiss()
}
}
}
}
}

View File

@ -0,0 +1,37 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.smsmessenger.databinding.DialogDeleteConfirmationBinding
class DeleteConfirmationDialog(
private val activity: Activity,
private val message: String,
private val showSkipRecycleBinOption: Boolean,
private val callback: (skipRecycleBin: Boolean) -> Unit
) {
private var dialog: AlertDialog? = null
val binding = DialogDeleteConfirmationBinding.inflate(activity.layoutInflater)
init {
binding.deleteRememberTitle.text = message
binding.skipTheRecycleBinCheckbox.beGoneIf(!showSkipRecycleBinOption)
activity.getAlertDialogBuilder()
.setPositiveButton(com.simplemobiletools.commons.R.string.yes) { _, _ -> dialogConfirmed() }
.setNegativeButton(com.simplemobiletools.commons.R.string.no, null)
.apply {
activity.setupDialogStuff(binding.root, this) { alertDialog ->
dialog = alertDialog
}
}
}
private fun dialogConfirmed() {
dialog?.dismiss()
callback(binding.skipTheRecycleBinCheckbox.isChecked)
}
}

View File

@ -1,74 +1,44 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.databinding.DialogExportMessagesBinding
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.helpers.EXPORT_FILE_EXT
import kotlinx.android.synthetic.main.dialog_export_messages.view.*
import java.io.File
class ExportMessagesDialog(
private val activity: SimpleActivity,
private val path: String,
private val hidePath: Boolean,
private val callback: (file: File) -> Unit,
private val callback: (fileName: String) -> Unit,
) {
private var realPath = if (path.isEmpty()) activity.internalStoragePath else path
private val config = activity.config
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_messages, null) as ViewGroup).apply {
export_messages_folder.setText(activity.humanizePath(realPath))
export_messages_filename.setText("${activity.getString(R.string.messages)}_${activity.getCurrentFormattedDateTime()}")
export_sms_checkbox.isChecked = config.exportSms
export_mms_checkbox.isChecked = config.exportMms
if (hidePath) {
export_messages_folder_hint.beGone()
} else {
export_messages_folder.setOnClickListener {
activity.hideKeyboard(export_messages_filename)
FilePickerDialog(activity, realPath, false, showFAB = true) {
export_messages_folder.setText(activity.humanizePath(it))
realPath = it
}
}
}
val binding = DialogExportMessagesBinding.inflate(activity.layoutInflater).apply {
exportSmsCheckbox.isChecked = config.exportSms
exportMmsCheckbox.isChecked = config.exportMms
exportMessagesFilename.setText(
activity.getString(R.string.messages) + "_" + activity.getCurrentFormattedDateTime()
)
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog ->
alertDialog.showKeyboard(view.export_messages_filename)
activity.setupDialogStuff(binding.root, this, R.string.export_messages) { alertDialog ->
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.export_messages_filename.value
config.exportSms = binding.exportSmsCheckbox.isChecked
config.exportMms = binding.exportMmsCheckbox.isChecked
val filename = binding.exportMessagesFilename.value
when {
filename.isEmpty() -> activity.toast(R.string.empty_name)
filename.isEmpty() -> activity.toast(com.simplemobiletools.commons.R.string.empty_name)
filename.isAValidFilename() -> {
val file = File(realPath, "$filename$EXPORT_FILE_EXT")
if (!hidePath && file.exists()) {
activity.toast(R.string.name_taken)
return@setOnClickListener
}
if (!view.export_sms_checkbox.isChecked && !view.export_mms_checkbox.isChecked) {
activity.toast(R.string.no_option_selected)
return@setOnClickListener
}
config.exportSms = view.export_sms_checkbox.isChecked
config.exportMms = view.export_mms_checkbox.isChecked
config.lastExportPath = file.absolutePath.getParentPath()
callback(file)
callback(filename)
alertDialog.dismiss()
}
else -> activity.toast(R.string.invalid_name)
else -> activity.toast(com.simplemobiletools.commons.R.string.invalid_name)
}
}
}

View File

@ -1,6 +1,5 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
@ -8,47 +7,47 @@ import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.databinding.DialogImportMessagesBinding
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
import kotlinx.android.synthetic.main.dialog_import_messages.view.*
import com.simplemobiletools.smsmessenger.models.ImportResult
import com.simplemobiletools.smsmessenger.models.MessagesBackup
class ImportMessagesDialog(
private val activity: SimpleActivity,
private val path: String,
private val messages: List<MessagesBackup>,
) {
private val config = activity.config
init {
var ignoreClicks = false
val view = (activity.layoutInflater.inflate(R.layout.dialog_import_messages, null) as ViewGroup).apply {
import_sms_checkbox.isChecked = config.importSms
import_mms_checkbox.isChecked = config.importMms
val binding = DialogImportMessagesBinding.inflate(activity.layoutInflater).apply {
importSmsCheckbox.isChecked = config.importSms
importMmsCheckbox.isChecked = config.importMms
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(view, this, R.string.import_messages) { alertDialog ->
activity.setupDialogStuff(binding.root, this, R.string.import_messages) { alertDialog ->
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (ignoreClicks) {
return@setOnClickListener
}
if (!view.import_sms_checkbox.isChecked && !view.import_mms_checkbox.isChecked) {
if (!binding.importSmsCheckbox.isChecked && !binding.importMmsCheckbox.isChecked) {
activity.toast(R.string.no_option_selected)
return@setOnClickListener
}
ignoreClicks = true
activity.toast(R.string.importing)
config.importSms = view.import_sms_checkbox.isChecked
config.importMms = view.import_mms_checkbox.isChecked
activity.toast(com.simplemobiletools.commons.R.string.importing)
config.importSms = binding.importSmsCheckbox.isChecked
config.importMms = binding.importMmsCheckbox.isChecked
ensureBackgroundThread {
MessagesImporter(activity).importMessages(path) {
MessagesImporter(activity).restoreMessages(messages) {
handleParseResult(it)
alertDialog.dismiss()
}
@ -58,12 +57,13 @@ class ImportMessagesDialog(
}
}
private fun handleParseResult(result: MessagesImporter.ImportResult) {
private fun handleParseResult(result: ImportResult) {
activity.toast(
when (result) {
IMPORT_OK -> R.string.importing_successful
IMPORT_PARTIAL -> R.string.importing_some_entries_failed
else -> R.string.no_items_found
ImportResult.IMPORT_OK -> com.simplemobiletools.commons.R.string.importing_successful
ImportResult.IMPORT_PARTIAL -> com.simplemobiletools.commons.R.string.importing_some_entries_failed
ImportResult.IMPORT_FAIL -> com.simplemobiletools.commons.R.string.importing_failed
else -> com.simplemobiletools.commons.R.string.no_items_found
}
)
}

View File

@ -3,19 +3,18 @@ package com.simplemobiletools.smsmessenger.dialogs
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.smsmessenger.R
import kotlinx.android.synthetic.main.dialog_invalid_number.view.*
import com.simplemobiletools.smsmessenger.databinding.DialogInvalidNumberBinding
class InvalidNumberDialog(val activity: BaseSimpleActivity, val text: String) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_invalid_number, null).apply {
dialog_invalid_number_desc.text = text
val binding = DialogInvalidNumberBinding.inflate(activity.layoutInflater).apply {
dialogInvalidNumberDesc.text = text
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok) { _, _ -> { } }
.setPositiveButton(com.simplemobiletools.commons.R.string.ok) { _, _ -> { } }
.apply {
activity.setupDialogStuff(view, this)
activity.setupDialogStuff(binding.root, this)
}
}
}

View File

@ -0,0 +1,148 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.view.*
import android.widget.PopupMenu
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.extensions.copyToClipboard
import com.simplemobiletools.commons.extensions.getPopupMenuTheme
import com.simplemobiletools.commons.extensions.getProperTextColor
import com.simplemobiletools.commons.extensions.setupViewBackground
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.ItemManageBlockedKeywordBinding
import com.simplemobiletools.smsmessenger.extensions.config
class ManageBlockedKeywordsAdapter(
activity: BaseSimpleActivity, var blockedKeywords: ArrayList<String>, val listener: RefreshRecyclerViewListener?,
recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
init {
setupDragListener(true)
}
override fun getActionMenuId() = R.menu.cab_blocked_keywords
override fun prepareActionMode(menu: Menu) {
menu.apply {
findItem(R.id.cab_copy_keyword).isVisible = isOneItemSelected()
}
}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
when (id) {
R.id.cab_copy_keyword -> copyKeywordToClipboard()
R.id.cab_delete -> deleteSelection()
}
}
override fun getSelectableItemCount() = blockedKeywords.size
override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = blockedKeywords.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = blockedKeywords.indexOfFirst { it.hashCode() == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemManageBlockedKeywordBinding.inflate(layoutInflater, parent, false)
return createViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val blockedKeyword = blockedKeywords[position]
holder.bindView(blockedKeyword, allowSingleClick = true, allowLongClick = true) { itemView, _ ->
setupView(itemView, blockedKeyword)
}
bindViewHolder(holder)
}
override fun getItemCount() = blockedKeywords.size
private fun getSelectedItems() = blockedKeywords.filter { selectedKeys.contains(it.hashCode()) }
private fun setupView(view: View, blockedKeyword: String) {
ItemManageBlockedKeywordBinding.bind(view).apply {
root.setupViewBackground(activity)
manageBlockedKeywordHolder.isSelected = selectedKeys.contains(blockedKeyword.hashCode())
manageBlockedKeywordTitle.apply {
text = blockedKeyword
setTextColor(textColor)
}
overflowMenuIcon.drawable.apply {
mutate()
setTint(activity.getProperTextColor())
}
overflowMenuIcon.setOnClickListener {
showPopupMenu(overflowMenuAnchor, blockedKeyword)
}
}
}
private fun showPopupMenu(view: View, blockedKeyword: String) {
finishActMode()
val theme = activity.getPopupMenuTheme()
val contextTheme = ContextThemeWrapper(activity, theme)
PopupMenu(contextTheme, view, Gravity.END).apply {
inflate(getActionMenuId())
setOnMenuItemClickListener { item ->
val blockedKeywordId = blockedKeyword.hashCode()
when (item.itemId) {
R.id.cab_copy_keyword -> {
executeItemMenuOperation(blockedKeywordId) {
copyKeywordToClipboard()
}
}
R.id.cab_delete -> {
executeItemMenuOperation(blockedKeywordId) {
deleteSelection()
}
}
}
true
}
show()
}
}
private fun executeItemMenuOperation(blockedKeywordId: Int, callback: () -> Unit) {
selectedKeys.add(blockedKeywordId)
callback()
selectedKeys.remove(blockedKeywordId)
}
private fun copyKeywordToClipboard() {
val selectedKeyword = getSelectedItems().firstOrNull() ?: return
activity.copyToClipboard(selectedKeyword)
finishActMode()
}
private fun deleteSelection() {
val deleteBlockedKeywords = HashSet<String>(selectedKeys.size)
val positions = getSelectedItemPositions()
getSelectedItems().forEach {
deleteBlockedKeywords.add(it)
activity.config.removeBlockedKeyword(it)
}
blockedKeywords.removeAll(deleteBlockedKeywords)
removeSelectedItems(positions)
if (blockedKeywords.isEmpty()) {
listener?.refreshItems()
}
}
}

View File

@ -0,0 +1,74 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.annotation.SuppressLint
import android.telephony.SubscriptionInfo
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.BasePropertiesDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.subscriptionManagerCompat
import com.simplemobiletools.smsmessenger.models.Message
import org.joda.time.DateTime
class MessageDetailsDialog(val activity: BaseSimpleActivity, val message: Message) : BasePropertiesDialog(activity) {
init {
@SuppressLint("MissingPermission")
val availableSIMs = activity.subscriptionManagerCompat().activeSubscriptionInfoList
addProperty(message.getSenderOrReceiverLabel(), message.getSenderOrReceiverPhoneNumbers())
if (availableSIMs.count() > 1) {
addProperty(R.string.message_details_sim, message.getSIM(availableSIMs))
}
addProperty(message.getSentOrReceivedAtLabel(), message.getSentOrReceivedAt())
activity.getAlertDialogBuilder()
.setPositiveButton(com.simplemobiletools.commons.R.string.ok) { _, _ -> }
.apply {
activity.setupDialogStuff(mDialogView.root, this, R.string.message_details)
}
}
private fun Message.getSenderOrReceiverLabel(): Int {
return if (isReceivedMessage()) {
R.string.message_details_sender
} else {
R.string.message_details_receiver
}
}
private fun Message.getSenderOrReceiverPhoneNumbers(): String {
return if (isReceivedMessage()) {
formatContactInfo(senderName, senderPhoneNumber)
} else {
participants.joinToString(", ") {
formatContactInfo(it.name, it.phoneNumbers.first().value)
}
}
}
private fun formatContactInfo(name: String, phoneNumber: String): String {
return if (name != phoneNumber) {
"$name ($phoneNumber)"
} else {
phoneNumber
}
}
private fun Message.getSIM(availableSIMs: List<SubscriptionInfo>): String {
return availableSIMs.firstOrNull { it.subscriptionId == subscriptionId }?.displayName?.toString()
?: activity.getString(com.simplemobiletools.commons.R.string.unknown)
}
private fun Message.getSentOrReceivedAtLabel(): Int {
return if (isReceivedMessage()) {
R.string.message_details_received_at
} else {
R.string.message_details_sent_at
}
}
private fun Message.getSentOrReceivedAt(): String {
return DateTime(date * 1000L).toString("${activity.config.dateFormat} ${activity.getTimeFormat()}")
}
}

View File

@ -2,15 +2,14 @@ package com.simplemobiletools.smsmessenger.dialogs
import android.app.Activity
import android.content.DialogInterface.BUTTON_POSITIVE
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.DialogRenameConversationBinding
import com.simplemobiletools.smsmessenger.models.Conversation
import kotlinx.android.synthetic.main.dialog_rename_conversation.view.*
class RenameConversationDialog(
private val activity: Activity,
@ -20,8 +19,8 @@ class RenameConversationDialog(
private var dialog: AlertDialog? = null
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_rename_conversation, null) as ViewGroup).apply {
rename_conv_edit_text.apply {
val binding = DialogRenameConversationBinding.inflate(activity.layoutInflater).apply {
renameConvEditText.apply {
if (conversation.usesCustomTitle) {
setText(conversation.title)
}
@ -31,17 +30,17 @@ class RenameConversationDialog(
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(view, this, R.string.rename_conversation) { alertDialog ->
activity.setupDialogStuff(binding.root, this, R.string.rename_conversation) { alertDialog ->
dialog = alertDialog
alertDialog.showKeyboard(view.rename_conv_edit_text)
alertDialog.showKeyboard(binding.renameConvEditText)
alertDialog.getButton(BUTTON_POSITIVE).apply {
setOnClickListener {
val newTitle = view.rename_conv_edit_text.text.toString()
val newTitle = binding.renameConvEditText.text.toString()
if (newTitle.isEmpty()) {
activity.toast(R.string.empty_name)
activity.toast(com.simplemobiletools.commons.R.string.empty_name)
return@setOnClickListener
}

View File

@ -11,18 +11,18 @@ import com.google.android.material.timepicker.TimeFormat
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.ScheduleMessageDialogBinding
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.roundToClosestMultipleOf
import kotlinx.android.synthetic.main.schedule_message_dialog.view.*
import org.joda.time.DateTime
import java.util.*
import java.util.Calendar
class ScheduleMessageDialog(
private val activity: BaseSimpleActivity,
private var dateTime: DateTime? = null,
private val callback: (dateTime: DateTime?) -> Unit
) {
private val view = activity.layoutInflater.inflate(R.layout.schedule_message_dialog, null)
private val binding = ScheduleMessageDialogBinding.inflate(activity.layoutInflater)
private val textColor = activity.getProperTextColor()
private var previewDialog: AlertDialog? = null
@ -32,16 +32,16 @@ class ScheduleMessageDialog(
private val calendar = Calendar.getInstance()
init {
arrayOf(view.subtitle, view.edit_time, view.edit_date).forEach {
arrayOf(binding.subtitle, binding.editTime, binding.editDate).forEach {
it.setTextColor(textColor)
}
arrayOf(view.date_image, view.time_image).forEach {
arrayOf(binding.dateImage, binding.timeImage).forEach {
it.applyColorFilter(textColor)
}
view.edit_date.setOnClickListener { showDatePicker() }
view.edit_time.setOnClickListener { showTimePicker() }
binding.editDate.setOnClickListener { showDatePicker() }
binding.editTime.setOnClickListener { showTimePicker() }
val targetDateTime = dateTime ?: DateTime.now().plusHours(1)
updateTexts(targetDateTime)
@ -56,8 +56,8 @@ class ScheduleMessageDialog(
private fun updateTexts(dateTime: DateTime) {
val dateFormat = activity.config.dateFormat
val timeFormat = activity.getTimeFormat()
view.edit_date.text = dateTime.toString(dateFormat)
view.edit_time.text = dateTime.toString(timeFormat)
binding.editDate.text = dateTime.toString(dateFormat)
binding.editTime.text = dateTime.toString(timeFormat)
}
private fun showPreview() {
@ -66,11 +66,11 @@ class ScheduleMessageDialog(
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
.apply {
previewShown = true
activity.setupDialogStuff(view, this, R.string.schedule_message) { dialog ->
activity.setupDialogStuff(binding.root, this, R.string.schedule_message) { dialog ->
previewDialog = dialog
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (validateDateTime()) {
@ -99,7 +99,7 @@ class ScheduleMessageDialog(
datePicker.minDate = System.currentTimeMillis()
show()
getButton(AlertDialog.BUTTON_NEGATIVE).apply {
text = activity.getString(R.string.cancel)
text = activity.getString(com.simplemobiletools.commons.R.string.cancel)
setOnClickListener {
dismiss()
}
@ -136,7 +136,7 @@ class ScheduleMessageDialog(
).apply {
show()
getButton(AlertDialog.BUTTON_NEGATIVE).apply {
text = activity.getString(R.string.cancel)
text = activity.getString(com.simplemobiletools.commons.R.string.cancel)
setOnClickListener {
dismiss()
}

View File

@ -3,20 +3,19 @@ package com.simplemobiletools.smsmessenger.dialogs
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.smsmessenger.R
import kotlinx.android.synthetic.main.dialog_select_text.view.*
import com.simplemobiletools.smsmessenger.databinding.DialogSelectTextBinding
// helper dialog for selecting just a part of a message, not copying the whole into clipboard
class SelectTextDialog(val activity: BaseSimpleActivity, val text: String) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_select_text, null).apply {
dialog_select_text_value.text = text
val binding = DialogSelectTextBinding.inflate(activity.layoutInflater).apply {
dialogSelectTextValue.text = text
}
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.ok) { dialog, which -> { } }
.setPositiveButton(com.simplemobiletools.commons.R.string.ok) { _, _ -> { } }
.apply {
activity.setupDialogStuff(view, this)
activity.setupDialogStuff(binding.root, this)
}
}
}

View File

@ -11,8 +11,7 @@ import com.simplemobiletools.commons.helpers.IS_PRIVATE
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
import java.util.*
import java.util.Locale
fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) {
hideKeyboard()
@ -23,7 +22,7 @@ fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) {
startActivity(this)
callback?.invoke()
} catch (e: ActivityNotFoundException) {
toast(R.string.no_app_found)
toast(com.simplemobiletools.commons.R.string.no_app_found)
} catch (e: Exception) {
showErrorToast(e)
}
@ -44,7 +43,7 @@ fun Activity.launchViewIntent(uri: Uri, mimetype: String, filename: String) {
if (newMimetype.isNotEmpty() && mimetype != newMimetype) {
launchViewIntent(uri, newMimetype, filename)
} else {
toast(R.string.no_app_found)
toast(com.simplemobiletools.commons.R.string.no_app_found)
}
} catch (e: Exception) {
showErrorToast(e)

View File

@ -6,6 +6,7 @@ import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteException
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
@ -116,7 +117,23 @@ fun Context.getMessages(
SimpleContact(0, 0, participantPhoto.name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList())
}
val isMMS = false
val message = Message(id, body, type, status, ArrayList(participants), date, read, thread, isMMS, null, senderName, photoUri, subscriptionId)
val message =
Message(
id,
body,
type,
status,
ArrayList(participants),
date,
read,
thread,
isMMS,
null,
senderNumber,
senderName,
photoUri,
subscriptionId
)
messages.add(message)
}
@ -135,6 +152,7 @@ fun Context.getMessages(
.filter { it.participants.isNotEmpty() }
.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }
.sortedWith(compareBy<Message> { it.date }.thenBy { it.id })
.takeLast(limit)
.toMutableList() as ArrayList<Message>
return messages
@ -188,17 +206,34 @@ fun Context.getMMS(threadId: Long? = null, getImageResolutions: Boolean = false,
val isMMS = true
val attachment = getMmsAttachment(mmsId, getImageResolutions)
val body = attachment.text
var senderNumber = ""
var senderName = ""
var senderPhotoUri = ""
if (type != Mms.MESSAGE_BOX_SENT && type != Mms.MESSAGE_BOX_FAILED) {
val number = getMMSSender(mmsId)
val namePhoto = getNameAndPhotoFromPhoneNumber(number)
senderNumber = getMMSSender(mmsId)
val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber)
senderName = namePhoto.name
senderPhotoUri = namePhoto.photoUri ?: ""
}
val message = Message(mmsId, body, type, status, participants, date, read, threadId, isMMS, attachment, senderName, senderPhotoUri, subscriptionId)
val message =
Message(
mmsId,
body,
type,
status,
participants,
date,
read,
threadId,
isMMS,
attachment,
senderNumber,
senderName,
senderPhotoUri,
subscriptionId
)
messages.add(message)
participants.forEach {
@ -228,15 +263,21 @@ fun Context.getMMSSender(msgId: Long): String {
}
fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<SimpleContact> = ArrayList()): ArrayList<Conversation> {
val archiveAvailable = config.isArchiveAvailable
val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true")
val projection = arrayOf(
val projection = mutableListOf(
Threads._ID,
Threads.SNIPPET,
Threads.DATE,
Threads.READ,
Threads.RECIPIENT_IDS
Threads.RECIPIENT_IDS,
)
if (archiveAvailable) {
projection += Threads.ARCHIVED
}
var selection = "${Threads.MESSAGE_COUNT} > ?"
var selectionArgs = arrayOf("0")
if (threadId != null) {
@ -249,38 +290,68 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<
val conversations = ArrayList<Conversation>()
val simpleContactHelper = SimpleContactsHelper(this)
val blockedNumbers = getBlockedNumbers()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor ->
val id = cursor.getLongValue(Threads._ID)
var snippet = cursor.getStringValue(Threads.SNIPPET) ?: ""
if (snippet.isEmpty()) {
snippet = getThreadSnippet(id)
}
try {
queryCursorUnsafe(uri, projection.toTypedArray(), selection, selectionArgs, sortOrder) { cursor ->
val id = cursor.getLongValue(Threads._ID)
var snippet = cursor.getStringValue(Threads.SNIPPET) ?: ""
if (snippet.isEmpty()) {
snippet = getThreadSnippet(id)
}
var date = cursor.getLongValue(Threads.DATE)
if (date.toString().length > 10) {
date /= 1000
}
var date = cursor.getLongValue(Threads.DATE)
if (date.toString().length > 10) {
date /= 1000
}
val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS)
val recipientIds = rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList()
val phoneNumbers = getThreadPhoneNumbers(recipientIds)
if (phoneNumbers.isEmpty() || phoneNumbers.any { isNumberBlocked(it, blockedNumbers) }) {
return@queryCursor
}
val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS)
val recipientIds = rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList()
val phoneNumbers = getThreadPhoneNumbers(recipientIds)
if (phoneNumbers.isEmpty() || phoneNumbers.any { isNumberBlocked(it, blockedNumbers) }) {
return@queryCursorUnsafe
}
val names = getThreadContactNames(phoneNumbers, privateContacts)
val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else ""
val isGroupConversation = phoneNumbers.size > 1
val read = cursor.getIntValue(Threads.READ) == 1
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first())
conversations.add(conversation)
val names = getThreadContactNames(phoneNumbers, privateContacts)
val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else ""
val isGroupConversation = phoneNumbers.size > 1
val read = cursor.getIntValue(Threads.READ) == 1
val archived = if (archiveAvailable) cursor.getIntValue(Threads.ARCHIVED) == 1 else false
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first(), isArchived = archived)
conversations.add(conversation)
}
} catch (sqliteException: SQLiteException) {
if (sqliteException.message?.contains("no such column: archived") == true && archiveAvailable) {
config.isArchiveAvailable = false
return getConversations(threadId, privateContacts)
} else {
showErrorToast(sqliteException)
}
} catch (e: Exception) {
showErrorToast(e)
}
conversations.sortByDescending { it.date }
return conversations
}
private fun Context.queryCursorUnsafe(
uri: Uri,
projection: Array<String>,
selection: String? = null,
selectionArgs: Array<String>? = null,
sortOrder: String? = null,
callback: (cursor: Cursor) -> Unit
) {
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
cursor?.use {
if (cursor.moveToFirst()) {
do {
callback(cursor)
} while (cursor.moveToNext())
}
}
}
fun Context.getConversationIds(): List<Long> {
val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true")
val projection = arrayOf(Threads._ID)
@ -556,7 +627,16 @@ fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto {
return NamePhoto(number, null)
}
fun Context.insertNewSMS(address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int): Long {
fun Context.insertNewSMS(
address: String,
subject: String,
body: String,
date: Long,
read: Int,
threadId: Long,
type: Int,
subscriptionId: Int
): Long {
val uri = Sms.CONTENT_URI
val contentValues = ContentValues().apply {
put(Sms.ADDRESS, address)
@ -577,6 +657,20 @@ fun Context.insertNewSMS(address: String, subject: String, body: String, date: L
}
}
fun Context.removeAllArchivedConversations(callback: (() -> Unit)? = null) {
ensureBackgroundThread {
try {
for (conversation in conversationsDB.getAllArchived()) {
deleteConversation(conversation.threadId)
}
toast(R.string.archive_emptied_successfully)
callback?.invoke()
} catch (e: Exception) {
toast(com.simplemobiletools.commons.R.string.unknown_error_occurred)
}
}
}
fun Context.deleteConversation(threadId: Long) {
var uri = Sms.CONTENT_URI
val selection = "${Sms.THREAD_ID} = ?"
@ -598,6 +692,79 @@ fun Context.deleteConversation(threadId: Long) {
messagesDB.deleteThreadMessages(threadId)
}
fun Context.checkAndDeleteOldRecycleBinMessages(callback: (() -> Unit)? = null) {
if (config.useRecycleBin && config.lastRecycleBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) {
config.lastRecycleBinCheck = System.currentTimeMillis()
ensureBackgroundThread {
try {
for (message in messagesDB.getOldRecycleBinMessages(System.currentTimeMillis() - MONTH_SECONDS * 1000L)) {
deleteMessage(message.id, message.isMMS)
}
callback?.invoke()
} catch (e: Exception) {
}
}
}
}
fun Context.emptyMessagesRecycleBin() {
val messages = messagesDB.getAllRecycleBinMessages()
for (message in messages) {
deleteMessage(message.id, message.isMMS)
}
}
fun Context.emptyMessagesRecycleBinForConversation(threadId: Long) {
val messages = messagesDB.getThreadMessagesFromRecycleBin(threadId)
for (message in messages) {
deleteMessage(message.id, message.isMMS)
}
}
fun Context.restoreAllMessagesFromRecycleBinForConversation(threadId: Long) {
messagesDB.deleteThreadMessagesFromRecycleBin(threadId)
}
fun Context.moveMessageToRecycleBin(id: Long) {
try {
messagesDB.insertRecycleBinEntry(RecycleBinMessage(id, System.currentTimeMillis()))
} catch (e: Exception) {
showErrorToast(e)
}
}
fun Context.restoreMessageFromRecycleBin(id: Long) {
try {
messagesDB.deleteFromRecycleBin(id)
} catch (e: Exception) {
showErrorToast(e)
}
}
fun Context.updateConversationArchivedStatus(threadId: Long, archived: Boolean) {
val uri = Threads.CONTENT_URI
val values = ContentValues().apply {
put(Threads.ARCHIVED, archived)
}
val selection = "${Threads._ID} = ?"
val selectionArgs = arrayOf(threadId.toString())
try {
contentResolver.update(uri, values, selection, selectionArgs)
} catch (sqliteException: SQLiteException) {
if (sqliteException.message?.contains("no such column: archived") == true && config.isArchiveAvailable) {
config.isArchiveAvailable = false
return
} else {
throw sqliteException
}
}
if (archived) {
conversationsDB.moveToArchive(threadId)
} else {
conversationsDB.unarchive(threadId)
}
}
fun Context.deleteMessage(id: Long, isMMS: Boolean) {
val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI
val selection = "${Sms._ID} = ?"
@ -682,13 +849,13 @@ fun Context.getThreadId(addresses: Set<String>): Long {
}
}
fun Context.showReceivedMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?) {
fun Context.showReceivedMessageNotification(messageId: Long, address: String, body: String, threadId: Long, bitmap: Bitmap?) {
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
ensureBackgroundThread {
val senderName = getNameFromAddress(address, privateCursor)
Handler(Looper.getMainLooper()).post {
notificationHelper.showMessageNotification(address, body, threadId, bitmap, senderName)
notificationHelper.showMessageNotification(messageId, address, body, threadId, bitmap, senderName)
}
}
}
@ -865,12 +1032,16 @@ fun Context.getFileSizeFromUri(uri: Uri): Long {
// fix a glitch at enabling Release version minifying from 5.12.3
// reset messages in 5.14.3 again, as PhoneNumber is no longer minified
fun Context.clearAllMessagesIfNeeded() {
// reset messages in 5.19.1 again, as SimpleContact is no longer minified
fun Context.clearAllMessagesIfNeeded(callback: () -> Unit) {
if (!config.wasDbCleared) {
ensureBackgroundThread {
messagesDB.deleteAll()
config.wasDbCleared = true
Handler(Looper.getMainLooper()).post(callback)
}
config.wasDbCleared = true
} else {
callback()
}
}
@ -926,7 +1097,8 @@ fun Context.createTemporaryThread(message: Message, threadId: Long = generateRan
isGroupConversation = addresses.size > 1,
phoneNumber = addresses.first(),
isScheduled = true,
usesCustomTitle = cachedConv?.usesCustomTitle == true
usesCustomTitle = cachedConv?.usesCustomTitle == true,
isArchived = false
)
try {
conversationsDB.insertOrUpdate(conversation)

View File

@ -2,26 +2,44 @@ package com.simplemobiletools.smsmessenger.helpers
import android.app.Activity
import android.net.Uri
import android.view.View
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentDocumentBinding
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentDocumentPreviewBinding
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentVcardBinding
import com.simplemobiletools.smsmessenger.databinding.ItemAttachmentVcardPreviewBinding
import com.simplemobiletools.smsmessenger.extensions.*
import kotlinx.android.synthetic.main.item_attachment_document.view.*
import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
import kotlinx.android.synthetic.main.item_attachment_vcard_preview.view.*
import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
fun View.setupDocumentPreview(
fun ItemAttachmentDocumentPreviewBinding.setupDocumentPreview(
uri: Uri,
title: String,
mimeType: String,
attachment: Boolean = false,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
onRemoveButtonClicked: (() -> Unit)? = null
) {
documentAttachmentHolder.setupDocumentPreview(uri, title, mimeType, onClick, onLongClick)
removeAttachmentButtonHolder.removeAttachmentButton.apply {
beVisible()
background.applyColorFilter(context.getProperPrimaryColor())
if (onRemoveButtonClicked != null) {
setOnClickListener {
onRemoveButtonClicked.invoke()
}
}
}
}
fun ItemAttachmentDocumentBinding.setupDocumentPreview(
uri: Uri,
title: String,
mimeType: String,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null
) {
val context = root.context
if (title.isNotEmpty()) {
filename.text = title
}
@ -29,13 +47,13 @@ fun View.setupDocumentPreview(
ensureBackgroundThread {
try {
val size = context.getFileSizeFromUri(uri)
post {
file_size.beVisible()
file_size.text = size.formatSize()
root.post {
fileSize.beVisible()
fileSize.text = size.formatSize()
}
} catch (e: Exception) {
post {
file_size.beGone()
root.post {
fileSize.beGone()
}
}
}
@ -43,18 +61,36 @@ fun View.setupDocumentPreview(
val textColor = context.getProperTextColor()
val primaryColor = context.getProperPrimaryColor()
document_attachment_holder.background.applyColorFilter(textColor)
filename.setTextColor(textColor)
file_size.setTextColor(textColor)
fileSize.setTextColor(textColor)
icon.setImageResource(getIconResourceForMimeType(mimeType))
icon.background.setTint(primaryColor)
document_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
root.background.applyColorFilter(primaryColor.darkenColor())
if (attachment) {
remove_attachment_button.apply {
root.setOnClickListener {
onClick?.invoke()
}
root.setOnLongClickListener {
onLongClick?.invoke()
true
}
}
fun ItemAttachmentVcardPreviewBinding.setupVCardPreview(
activity: Activity,
uri: Uri,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
onRemoveButtonClicked: (() -> Unit)? = null,
) {
vcardProgress.beVisible()
vcardAttachmentHolder.setupVCardPreview(activity = activity, uri = uri, attachment = true, onClick = onClick, onLongClick = onLongClick) {
vcardProgress.beGone()
removeAttachmentButtonHolder.removeAttachmentButton.apply {
beVisible()
background.applyColorFilter(primaryColor)
background.applyColorFilter(activity.getProperPrimaryColor())
if (onRemoveButtonClicked != null) {
setOnClickListener {
onRemoveButtonClicked.invoke()
@ -62,45 +98,36 @@ fun View.setupDocumentPreview(
}
}
}
document_attachment_holder.setOnClickListener {
onClick?.invoke()
}
document_attachment_holder.setOnLongClickListener {
onLongClick?.invoke()
true
}
}
fun View.setupVCardPreview(
fun ItemAttachmentVcardBinding.setupVCardPreview(
activity: Activity,
uri: Uri,
attachment: Boolean = false,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
onRemoveButtonClicked: (() -> Unit)? = null,
onVCardLoaded: (() -> Unit)? = null,
) {
val context = root.context
val textColor = activity.getProperTextColor()
val primaryColor = activity.getProperPrimaryColor()
vcard_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
vcard_title.setTextColor(textColor)
vcard_subtitle.setTextColor(textColor)
root.background.applyColorFilter(primaryColor.darkenColor())
vcardTitle.setTextColor(textColor)
vcardSubtitle.setTextColor(textColor)
if (attachment) {
vcard_progress.beVisible()
}
arrayOf(vcard_photo, vcard_title, vcard_subtitle, view_contact_details).forEach {
arrayOf(vcardPhoto, vcardTitle, vcardSubtitle, viewContactDetails).forEach {
it.beGone()
}
parseVCardFromUri(activity, uri) { vCards ->
activity.runOnUiThread {
if (vCards.isEmpty()) {
vcard_title.beVisible()
vcard_title.text = context.getString(R.string.unknown_error_occurred)
vcardTitle.beVisible()
vcardTitle.text = context.getString(com.simplemobiletools.commons.R.string.unknown_error_occurred)
return@runOnUiThread
}
val title = vCards.firstOrNull()?.parseNameFromVCard()
val imageIcon = if (title != null) {
SimpleContactsHelper(activity).getContactLetterIcon(title)
@ -108,41 +135,32 @@ fun View.setupVCardPreview(
null
}
arrayOf(vcard_photo, vcard_title).forEach {
arrayOf(vcardPhoto, vcardTitle).forEach {
it.beVisible()
}
vcard_photo.setImageBitmap(imageIcon)
vcard_title.text = title
vcardPhoto.setImageBitmap(imageIcon)
vcardTitle.text = title
if (vCards.size > 1) {
vcard_subtitle.beVisible()
vcardSubtitle.beVisible()
val quantity = vCards.size - 1
vcard_subtitle.text = resources.getQuantityString(R.plurals.and_other_contacts, quantity, quantity)
vcardSubtitle.text = context.resources.getQuantityString(R.plurals.and_other_contacts, quantity, quantity)
} else {
vcard_subtitle.beGone()
vcardSubtitle.beGone()
}
if (attachment) {
vcard_progress.beGone()
remove_attachment_button.apply {
beVisible()
background.applyColorFilter(primaryColor)
if (onRemoveButtonClicked != null) {
setOnClickListener {
onRemoveButtonClicked.invoke()
}
}
}
onVCardLoaded?.invoke()
} else {
view_contact_details.setTextColor(primaryColor)
view_contact_details.beVisible()
viewContactDetails.setTextColor(primaryColor)
viewContactDetails.beVisible()
}
vcard_attachment_holder.setOnClickListener {
vcardAttachmentHolder.setOnClickListener {
onClick?.invoke()
}
vcard_attachment_holder.setOnLongClickListener {
vcardAttachmentHolder.setOnLongClickListener {
onLongClick?.invoke()
true
}

View File

@ -68,6 +68,18 @@ class Config(context: Context) : BaseConfig(context) {
pinnedConversations = pinnedConversations.minus(conversations.map { it.threadId.toString() })
}
var blockedKeywords: Set<String>
get() = prefs.getStringSet(BLOCKED_KEYWORDS, HashSet<String>())!!
set(blockedKeywords) = prefs.edit().putStringSet(BLOCKED_KEYWORDS, blockedKeywords).apply()
fun addBlockedKeyword(keyword: String) {
blockedKeywords = blockedKeywords.plus(keyword)
}
fun removeBlockedKeyword(keyword: String) {
blockedKeywords = blockedKeywords.minus(keyword)
}
var exportSms: Boolean
get() = prefs.getBoolean(EXPORT_SMS, true)
set(exportSms) = prefs.edit().putBoolean(EXPORT_SMS, exportSms).apply()
@ -91,4 +103,16 @@ class Config(context: Context) : BaseConfig(context) {
var keyboardHeight: Int
get() = prefs.getInt(SOFT_KEYBOARD_HEIGHT, context.getDefaultKeyboardHeight())
set(keyboardHeight) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, keyboardHeight).apply()
var useRecycleBin: Boolean
get() = prefs.getBoolean(USE_RECYCLE_BIN, false)
set(useRecycleBin) = prefs.edit().putBoolean(USE_RECYCLE_BIN, useRecycleBin).apply()
var lastRecycleBinCheck: Long
get() = prefs.getLong(LAST_RECYCLE_BIN_CHECK, 0L)
set(lastRecycleBinCheck) = prefs.edit().putLong(LAST_RECYCLE_BIN_CHECK, lastRecycleBinCheck).apply()
var isArchiveAvailable: Boolean
get() = prefs.getBoolean(IS_ARCHIVE_AVAILABLE, true)
set(isArchiveAvailable) = prefs.edit().putBoolean(IS_ARCHIVE_AVAILABLE, isArchiveAvailable).apply()
}

View File

@ -25,24 +25,30 @@ const val SEND_LONG_MESSAGE_MMS = "send_long_message_mms"
const val SEND_GROUP_MESSAGE_MMS = "send_group_message_mms"
const val MMS_FILE_SIZE_LIMIT = "mms_file_size_limit"
const val PINNED_CONVERSATIONS = "pinned_conversations"
const val LAST_EXPORT_PATH = "last_export_path"
const val BLOCKED_KEYWORDS = "blocked_keywords"
const val EXPORT_SMS = "export_sms"
const val EXPORT_MMS = "export_mms"
const val EXPORT_MIME_TYPE = "application/json"
const val EXPORT_FILE_EXT = ".json"
const val JSON_FILE_EXTENSION = ".json"
const val JSON_MIME_TYPE = "application/json"
const val XML_MIME_TYPE = "text/xml"
const val TXT_MIME_TYPE = "text/plain"
const val IMPORT_SMS = "import_sms"
const val IMPORT_MMS = "import_mms"
const val WAS_DB_CLEARED = "was_db_cleared_2"
const val WAS_DB_CLEARED = "was_db_cleared_4"
const val EXTRA_VCARD_URI = "vcard"
const val SCHEDULED_MESSAGE_ID = "scheduled_message_id"
const val SOFT_KEYBOARD_HEIGHT = "soft_keyboard_height"
const val IS_MMS = "is_mms"
const val MESSAGE_ID = "message_id"
const val USE_RECYCLE_BIN = "use_recycle_bin"
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
const val IS_RECYCLE_BIN = "is_recycle_bin"
const val IS_ARCHIVE_AVAILABLE = "is_archive_available"
private const val PATH = "com.simplemobiletools.smsmessenger.action."
const val MARK_AS_READ = PATH + "mark_as_read"
const val REPLY = PATH + "reply"
const val DATE_FORMAT_PATTERN = "dd MMM, YYYY"
// view types for the thread list view
const val THREAD_DATE_TIME = 1
const val THREAD_RECEIVED_MESSAGE = 2
@ -70,7 +76,7 @@ const val FILE_SIZE_600_KB = 614_400L
const val FILE_SIZE_1_MB = 1_048_576L
const val FILE_SIZE_2_MB = 2_097_152L
const val MESSAGES_LIMIT = 75
const val MESSAGES_LIMIT = 30
// intent launch request codes
const val PICK_PHOTO_INTENT = 42

View File

@ -1,68 +0,0 @@
package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import com.google.gson.Gson
import com.google.gson.stream.JsonWriter
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.getConversationIds
import java.io.OutputStream
class MessagesExporter(private val context: Context) {
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK
}
private val config = context.config
private val messageReader = MessagesReader(context)
private val gson = Gson()
fun exportMessages(outputStream: OutputStream?, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ExportResult) -> Unit) {
ensureBackgroundThread {
if (outputStream == null) {
callback.invoke(ExportResult.EXPORT_FAIL)
return@ensureBackgroundThread
}
val writer = JsonWriter(outputStream.bufferedWriter())
writer.use {
try {
var written = 0
writer.beginArray()
val conversationIds = context.getConversationIds()
val totalMessages = messageReader.getMessagesCount()
for (threadId in conversationIds) {
writer.beginObject()
if (config.exportSms && messageReader.getSmsCount() > 0) {
writer.name("sms")
writer.beginArray()
messageReader.forEachSms(threadId) {
writer.jsonValue(gson.toJson(it))
written++
onProgress.invoke(totalMessages, written)
}
writer.endArray()
}
if (config.exportMms && messageReader.getMmsCount() > 0) {
writer.name("mms")
writer.beginArray()
messageReader.forEachMms(threadId) {
writer.jsonValue(gson.toJson(it))
written++
onProgress.invoke(totalMessages, written)
}
writer.endArray()
}
writer.endObject()
}
writer.endArray()
callback.invoke(ExportResult.EXPORT_OK)
} catch (e: Exception) {
callback.invoke(ExportResult.EXPORT_FAIL)
}
}
}
}
}

View File

@ -1,77 +1,197 @@
package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import android.provider.Telephony.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import android.net.Uri
import android.util.Xml
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.*
import com.simplemobiletools.smsmessenger.models.ExportedMessage
import java.io.File
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.models.*
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import org.xmlpull.v1.XmlPullParser
import java.io.InputStream
class MessagesImporter(private val context: Context) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}
private val gson = Gson()
private val messageWriter = MessagesWriter(context)
private val config = context.config
class MessagesImporter(private val activity: SimpleActivity) {
private val messageWriter = MessagesWriter(activity)
private val config = activity.config
private var messagesImported = 0
private var messagesFailed = 0
fun importMessages(path: String, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ImportResult) -> Unit) {
fun importMessages(uri: Uri) {
try {
val fileType = activity.contentResolver.getType(uri).orEmpty()
val isXml = isXmlMimeType(fileType) || (uri.path?.endsWith("txt") == true && isFileXml(uri))
if (isXml) {
activity.toast(com.simplemobiletools.commons.R.string.importing)
getInputStreamFromUri(uri)!!.importXml()
} else {
importJson(uri)
}
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
private fun importJson(uri: Uri) {
try {
val jsonString = activity.contentResolver.openInputStream(uri)!!.use { inputStream ->
inputStream.bufferedReader().readText()
}
val deserializedList = Json.decodeFromString<List<MessagesBackup>>(jsonString)
if (deserializedList.isEmpty()) {
activity.toast(com.simplemobiletools.commons.R.string.no_entries_for_importing)
return
}
ImportMessagesDialog(activity, deserializedList)
} catch (e: SerializationException) {
activity.toast(com.simplemobiletools.commons.R.string.invalid_file_format)
} catch (e: IllegalArgumentException) {
activity.toast(com.simplemobiletools.commons.R.string.invalid_file_format)
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
fun restoreMessages(messagesBackup: List<MessagesBackup>, callback: (ImportResult) -> Unit) {
ensureBackgroundThread {
try {
val inputStream = if (path.contains("/")) {
File(path).inputStream()
} else {
context.assets.open(path)
}
inputStream.bufferedReader().use { reader ->
val json = reader.readText()
val type = object : TypeToken<List<ExportedMessage>>() {}.type
val messages = gson.fromJson<List<ExportedMessage>>(json, type)
val totalMessages = messages.flatMap { it.sms ?: emptyList() }.size + messages.flatMap { it.mms ?: emptyList() }.size
if (totalMessages <= 0) {
callback.invoke(IMPORT_NOTHING_NEW)
return@ensureBackgroundThread
}
onProgress.invoke(totalMessages, messagesImported)
for (message in messages) {
if (config.importSms) {
message.sms?.forEach { backup ->
messageWriter.writeSmsMessage(backup)
messagesImported++
onProgress.invoke(totalMessages, messagesImported)
}
messagesBackup.forEach { message ->
try {
if (message.backupType == BackupType.SMS && config.importSms) {
messageWriter.writeSmsMessage(message as SmsBackup)
messagesImported++
} else if (message.backupType == BackupType.MMS && config.importMms) {
messageWriter.writeMmsMessage(message as MmsBackup)
messagesImported++
}
if (config.importMms) {
message.mms?.forEach { backup ->
messageWriter.writeMmsMessage(backup)
messagesImported++
onProgress.invoke(totalMessages, messagesImported)
}
}
refreshMessages()
} catch (e: Exception) {
activity.showErrorToast(e)
messagesFailed++
}
}
refreshMessages()
} catch (e: Exception) {
context.showErrorToast(e)
messagesFailed++
activity.showErrorToast(e)
}
callback.invoke(
when {
messagesImported == 0 -> IMPORT_FAIL
messagesFailed > 0 -> IMPORT_PARTIAL
else -> IMPORT_OK
messagesImported == 0 && messagesFailed == 0 -> ImportResult.IMPORT_NOTHING_NEW
messagesFailed > 0 && messagesImported > 0 -> ImportResult.IMPORT_PARTIAL
messagesFailed > 0 -> ImportResult.IMPORT_FAIL
else -> ImportResult.IMPORT_OK
}
)
}
}
private fun InputStream.importXml() {
try {
bufferedReader().use { reader ->
val xmlParser = Xml.newPullParser().apply {
setInput(reader)
}
xmlParser.nextTag()
xmlParser.require(XmlPullParser.START_TAG, null, "smses")
var depth = 1
while (depth != 0) {
when (xmlParser.next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
}
if (xmlParser.eventType != XmlPullParser.START_TAG) {
continue
}
try {
if (xmlParser.name == "sms") {
if (config.importSms) {
val message = xmlParser.readSms()
messageWriter.writeSmsMessage(message)
messagesImported++
} else {
xmlParser.skip()
}
} else {
xmlParser.skip()
}
} catch (e: Exception) {
activity.showErrorToast(e)
messagesFailed++
}
}
refreshMessages()
}
when {
messagesFailed > 0 && messagesImported > 0 -> activity.toast(com.simplemobiletools.commons.R.string.importing_some_entries_failed)
messagesFailed > 0 -> activity.toast(com.simplemobiletools.commons.R.string.importing_failed)
else -> activity.toast(com.simplemobiletools.commons.R.string.importing_successful)
}
} catch (_: Exception) {
activity.toast(com.simplemobiletools.commons.R.string.invalid_file_format)
}
}
private fun XmlPullParser.readSms(): SmsBackup {
require(XmlPullParser.START_TAG, null, "sms")
return SmsBackup(
subscriptionId = 0,
address = getAttributeValue(null, "address"),
body = getAttributeValue(null, "body"),
date = getAttributeValue(null, "date").toLong(),
dateSent = getAttributeValue(null, "date").toLong(),
locked = getAttributeValue(null, "locked").toInt(),
protocol = getAttributeValue(null, "protocol"),
read = getAttributeValue(null, "read").toInt(),
status = getAttributeValue(null, "status").toInt(),
type = getAttributeValue(null, "type").toInt(),
serviceCenter = getAttributeValue(null, "service_center")
)
}
private fun XmlPullParser.skip() {
if (eventType != XmlPullParser.START_TAG) {
throw IllegalStateException()
}
var depth = 1
while (depth != 0) {
when (next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
}
}
}
private fun getInputStreamFromUri(uri: Uri): InputStream? {
return try {
activity.contentResolver.openInputStream(uri)
} catch (e: Exception) {
null
}
}
private fun isFileXml(uri: Uri): Boolean {
val inputStream = getInputStreamFromUri(uri)
return inputStream?.bufferedReader()?.use { reader ->
reader.readLine()?.startsWith("<?xml") ?: false
} ?: false
}
private fun isXmlMimeType(mimeType: String): Boolean {
return mimeType.equals("application/xml", ignoreCase = true) || mimeType.equals("text/xml", ignoreCase = true)
}
private fun isJsonMimeType(mimeType: String): Boolean {
return mimeType.equals("application/json", ignoreCase = true)
}
}

View File

@ -9,15 +9,30 @@ import android.util.Base64
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.smsmessenger.models.MmsAddress
import com.simplemobiletools.smsmessenger.models.MmsBackup
import com.simplemobiletools.smsmessenger.models.MmsPart
import com.simplemobiletools.smsmessenger.models.SmsBackup
import com.simplemobiletools.smsmessenger.extensions.getConversationIds
import com.simplemobiletools.smsmessenger.models.*
import java.io.IOException
import java.io.InputStream
class MessagesReader(private val context: Context) {
fun forEachSms(threadId: Long, block: (SmsBackup) -> Unit) {
fun getMessagesToExport(
getSms: Boolean, getMms: Boolean, callback: (messages: List<MessagesBackup>) -> Unit
) {
val conversationIds = context.getConversationIds()
var smsMessages = listOf<SmsBackup>()
var mmsMessages = listOf<MmsBackup>()
if (getSms) {
smsMessages = getSmsMessages(conversationIds)
}
if (getMms) {
mmsMessages = getMmsMessages(conversationIds)
}
callback(smsMessages + mmsMessages)
}
private fun getSmsMessages(threadIds: List<Long>): List<SmsBackup> {
val projection = arrayOf(
Sms.SUBSCRIPTION_ID,
Sms.ADDRESS,
@ -33,25 +48,28 @@ class MessagesReader(private val context: Context) {
)
val selection = "${Sms.THREAD_ID} = ?"
val selectionArgs = arrayOf(threadId.toString())
context.queryCursor(Sms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID)
val address = cursor.getStringValue(Sms.ADDRESS)
val body = cursor.getStringValueOrNull(Sms.BODY)
val date = cursor.getLongValue(Sms.DATE)
val dateSent = cursor.getLongValue(Sms.DATE_SENT)
val locked = cursor.getIntValue(Sms.DATE_SENT)
val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL)
val read = cursor.getIntValue(Sms.READ)
val status = cursor.getIntValue(Sms.STATUS)
val type = cursor.getIntValue(Sms.TYPE)
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER)
block(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter))
val smsList = mutableListOf<SmsBackup>()
threadIds.map { it.toString() }.forEach { threadId ->
context.queryCursor(Sms.CONTENT_URI, projection, selection, arrayOf(threadId)) { cursor ->
val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID)
val address = cursor.getStringValue(Sms.ADDRESS)
val body = cursor.getStringValueOrNull(Sms.BODY)
val date = cursor.getLongValue(Sms.DATE)
val dateSent = cursor.getLongValue(Sms.DATE_SENT)
val locked = cursor.getIntValue(Sms.DATE_SENT)
val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL)
val read = cursor.getIntValue(Sms.READ)
val status = cursor.getIntValue(Sms.STATUS)
val type = cursor.getIntValue(Sms.TYPE)
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER)
smsList.add(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter))
}
}
return smsList
}
// all mms from simple sms are non-text messages
fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (MmsBackup) -> Unit) {
private fun getMmsMessages(threadIds: List<Long>, includeTextOnlyAttachment: Boolean = false): List<MmsBackup> {
val projection = arrayOf(
Mms._ID,
Mms.CREATOR,
@ -71,65 +89,67 @@ class MessagesReader(private val context: Context) {
Mms.SUBSCRIPTION_ID,
Mms.TRANSACTION_ID
)
val selection = if (includeTextOnlyAttachment) {
"${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?"
} else {
"${Mms.THREAD_ID} = ?"
}
val mmsList = mutableListOf<MmsBackup>()
val selectionArgs = if (includeTextOnlyAttachment) {
arrayOf(threadId.toString(), "1")
} else {
arrayOf(threadId.toString())
}
threadIds.map { it.toString() }.forEach { threadId ->
val selectionArgs = if (includeTextOnlyAttachment) {
arrayOf(threadId, "1")
} else {
arrayOf(threadId)
}
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
val mmsId = cursor.getLongValue(Mms._ID)
val creator = cursor.getStringValueOrNull(Mms.CREATOR)
val contentType = cursor.getStringValueOrNull(Mms.CONTENT_TYPE)
val deliveryReport = cursor.getIntValue(Mms.DELIVERY_REPORT)
val date = cursor.getLongValue(Mms.DATE)
val dateSent = cursor.getLongValue(Mms.DATE_SENT)
val locked = cursor.getIntValue(Mms.LOCKED)
val messageType = cursor.getIntValue(Mms.MESSAGE_TYPE)
val messageBox = cursor.getIntValue(Mms.MESSAGE_BOX)
val read = cursor.getIntValue(Mms.READ)
val readReport = cursor.getIntValue(Mms.READ_REPORT)
val seen = cursor.getIntValue(Mms.SEEN)
val textOnly = cursor.getIntValue(Mms.TEXT_ONLY)
val status = cursor.getStringValueOrNull(Mms.STATUS)
val subject = cursor.getStringValueOrNull(Mms.SUBJECT)
val subjectCharSet = cursor.getStringValueOrNull(Mms.SUBJECT_CHARSET)
val subscriptionId = cursor.getLongValue(Mms.SUBSCRIPTION_ID)
val transactionId = cursor.getStringValueOrNull(Mms.TRANSACTION_ID)
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
val mmsId = cursor.getLongValue(Mms._ID)
val creator = cursor.getStringValueOrNull(Mms.CREATOR)
val contentType = cursor.getStringValueOrNull(Mms.CONTENT_TYPE)
val deliveryReport = cursor.getIntValue(Mms.DELIVERY_REPORT)
val date = cursor.getLongValue(Mms.DATE)
val dateSent = cursor.getLongValue(Mms.DATE_SENT)
val locked = cursor.getIntValue(Mms.LOCKED)
val messageType = cursor.getIntValue(Mms.MESSAGE_TYPE)
val messageBox = cursor.getIntValue(Mms.MESSAGE_BOX)
val read = cursor.getIntValue(Mms.READ)
val readReport = cursor.getIntValue(Mms.READ_REPORT)
val seen = cursor.getIntValue(Mms.SEEN)
val textOnly = cursor.getIntValue(Mms.TEXT_ONLY)
val status = cursor.getStringValueOrNull(Mms.STATUS)
val subject = cursor.getStringValueOrNull(Mms.SUBJECT)
val subjectCharSet = cursor.getStringValueOrNull(Mms.SUBJECT_CHARSET)
val subscriptionId = cursor.getLongValue(Mms.SUBSCRIPTION_ID)
val transactionId = cursor.getStringValueOrNull(Mms.TRANSACTION_ID)
val parts = getParts(mmsId)
val addresses = getMmsAddresses(mmsId)
block(
MmsBackup(
creator,
contentType,
deliveryReport,
date,
dateSent,
locked,
messageType,
messageBox,
read,
readReport,
seen,
textOnly,
status,
subject,
subjectCharSet,
subscriptionId,
transactionId,
addresses,
parts
val parts = getParts(mmsId)
val addresses = getMmsAddresses(mmsId)
mmsList.add(
MmsBackup(
creator,
contentType,
deliveryReport,
date,
dateSent,
locked,
messageType,
messageBox,
read,
readReport,
seen,
textOnly,
status,
subject,
subjectCharSet,
subscriptionId,
transactionId,
addresses,
parts
)
)
)
}
}
return mmsList
}
@SuppressLint("NewApi")
@ -172,6 +192,7 @@ class MessagesReader(private val context: Context) {
stream.readBytes().toString(Charsets.UTF_8)
}
}
else -> {
usePart(partId) { stream ->
Base64.encodeToString(stream.readBytes(), Base64.DEFAULT)

View File

@ -23,6 +23,7 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
import com.simplemobiletools.smsmessenger.receivers.DeleteSmsReceiver
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
@ -35,7 +36,15 @@ class NotificationHelper(private val context: Context) {
.build()
@SuppressLint("NewApi")
fun showMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?, sender: String?, alertOnlyOnce: Boolean = false) {
fun showMessageNotification(
messageId: Long,
address: String,
body: String,
threadId: Long,
bitmap: Bitmap?,
sender: String?,
alertOnlyOnce: Boolean = false
) {
maybeCreateChannel(name = context.getString(R.string.channel_received_sms))
val notificationId = threadId.hashCode()
@ -52,8 +61,16 @@ class NotificationHelper(private val context: Context) {
val markAsReadPendingIntent =
PendingIntent.getBroadcast(context, notificationId, markAsReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
val deleteSmsIntent = Intent(context, DeleteSmsReceiver::class.java).apply {
putExtra(THREAD_ID, threadId)
putExtra(MESSAGE_ID, messageId)
}
val deleteSmsPendingIntent =
PendingIntent.getBroadcast(context, notificationId, deleteSmsIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
var replyAction: NotificationCompat.Action? = null
if (isNougatPlus() && !isShortCodeWithLetters(address)) {
val isNoReplySms = isShortCodeWithLetters(address)
if (isNougatPlus() && !isNoReplySms) {
val replyLabel = context.getString(R.string.reply)
val remoteInput = RemoteInput.Builder(REPLY)
.setLabel(replyLabel)
@ -87,6 +104,7 @@ class NotificationHelper(private val context: Context) {
setLargeIcon(largeIcon)
setStyle(getMessagesStyle(address, body, notificationId, sender))
}
LOCK_SCREEN_SENDER -> {
setContentTitle(sender)
setLargeIcon(largeIcon)
@ -110,9 +128,15 @@ class NotificationHelper(private val context: Context) {
builder.addAction(replyAction)
}
builder.addAction(R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent)
builder.addAction(com.simplemobiletools.commons.R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent)
.setChannelId(NOTIFICATION_CHANNEL)
if (isNoReplySms) {
builder.addAction(
com.simplemobiletools.commons.R.drawable.ic_delete_vector,
context.getString(com.simplemobiletools.commons.R.string.delete),
deleteSmsPendingIntent
).setChannelId(NOTIFICATION_CHANNEL)
}
notificationManager.notify(notificationId, builder.build())
}

View File

@ -1,18 +1,34 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.ConversationWithSnippetOverride
@Dao
interface ConversationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(conversation: Conversation): Long
@Query("SELECT * FROM conversations")
fun getAll(): List<Conversation>
@Query("SELECT (SELECT body FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND messages.thread_id = conversations.thread_id ORDER BY messages.date DESC LIMIT 1) as new_snippet, * FROM conversations WHERE archived = 0")
fun getNonArchivedWithLatestSnippet(): List<ConversationWithSnippetOverride>
fun getNonArchived(): List<Conversation> {
return getNonArchivedWithLatestSnippet().map { it.toConversation() }
}
@Query("SELECT (SELECT body FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND messages.thread_id = conversations.thread_id ORDER BY messages.date DESC LIMIT 1) as new_snippet, * FROM conversations WHERE archived = 1")
fun getAllArchivedWithLatestSnippet(): List<ConversationWithSnippetOverride>
fun getAllArchived(): List<Conversation> {
return getAllArchivedWithLatestSnippet().map { it.toConversation() }
}
@Query("SELECT (SELECT body FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND messages.thread_id = conversations.thread_id ORDER BY messages.date DESC LIMIT 1) as new_snippet, * FROM conversations WHERE (SELECT COUNT(*) FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND messages.thread_id = conversations.thread_id) > 0")
fun getAllWithMessagesInRecycleBinWithLatestSnippet(): List<ConversationWithSnippetOverride>
fun getAllWithMessagesInRecycleBin(): List<Conversation> {
return getAllWithMessagesInRecycleBinWithLatestSnippet().map { it.toConversation() }
}
@Query("SELECT * FROM conversations WHERE thread_id = :threadId")
fun getConversationWithThreadId(threadId: Long): Conversation?
@ -29,6 +45,12 @@ interface ConversationsDao {
@Query("UPDATE conversations SET read = 0 WHERE thread_id = :threadId")
fun markUnread(threadId: Long)
@Query("UPDATE conversations SET archived = 1 WHERE thread_id = :threadId")
fun moveToArchive(threadId: Long)
@Query("UPDATE conversations SET archived = 0 WHERE thread_id = :threadId")
fun unarchive(threadId: Long)
@Query("DELETE FROM conversations WHERE thread_id = :threadId")
fun deleteThreadId(threadId: Long)
}

View File

@ -1,9 +1,7 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.simplemobiletools.smsmessenger.models.RecycleBinMessage
import com.simplemobiletools.smsmessenger.models.Message
@Dao
@ -11,6 +9,9 @@ interface MessagesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(message: Message)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertRecycleBinEntry(recycleBinMessage: RecycleBinMessage)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertOrIgnore(message: Message): Long
@ -20,15 +21,30 @@ interface MessagesDao {
@Query("SELECT * FROM messages")
fun getAll(): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL")
fun getAllRecycleBinMessages(): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND recycle_bin_messages.deleted_ts < :timestamp")
fun getOldRecycleBinMessages(timestamp: Long): List<Message>
@Query("SELECT * FROM messages WHERE thread_id = :threadId")
fun getThreadMessages(threadId: Long): List<Message>
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled = 1")
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND thread_id = :threadId")
fun getNonRecycledThreadMessages(threadId: Long): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND thread_id = :threadId")
fun getThreadMessagesFromRecycleBin(threadId: Long): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND thread_id = :threadId AND is_scheduled = 1")
fun getScheduledThreadMessages(threadId: Long): List<Message>
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled = 1")
fun getScheduledMessageWithId(threadId: Long, messageId: Long): Message
@Query("SELECT COUNT(*) FROM recycle_bin_messages")
fun getArchivedCount(): Int
@Query("SELECT * FROM messages WHERE body LIKE :text")
fun getMessagesWithText(text: String): List<Message>
@ -44,11 +60,29 @@ interface MessagesDao {
@Query("UPDATE messages SET status = :status WHERE id = :id")
fun updateStatus(id: Long, status: Int): Int
@Transaction
fun delete(id: Long) {
deleteFromMessages(id)
deleteFromRecycleBin(id)
}
@Query("DELETE FROM messages WHERE id = :id")
fun delete(id: Long)
fun deleteFromMessages(id: Long)
@Query("DELETE FROM recycle_bin_messages WHERE id = :id")
fun deleteFromRecycleBin(id: Long)
@Transaction
fun deleteThreadMessages(threadId: Long) {
deleteThreadMessagesFromRecycleBin(threadId)
deleteAllThreadMessages(threadId)
}
@Query("DELETE FROM messages WHERE thread_id = :threadId")
fun deleteThreadMessages(threadId: Long)
fun deleteAllThreadMessages(threadId: Long)
@Query("DELETE FROM recycle_bin_messages WHERE id IN (SELECT id FROM messages WHERE thread_id = :threadId)")
fun deleteThreadMessagesFromRecycleBin(threadId: Long)
@Query("DELETE FROM messages")
fun deleteAll()

View File

@ -32,27 +32,33 @@ fun Context.isLongMmsMessage(text: String, settings: Settings = getSendMessageSe
}
/** Sends the message using the in-app SmsManager API wrappers if it's an SMS or using android-smsmms for MMS. */
fun Context.sendMessageCompat(text: String, addresses: List<String>, subId: Int?, attachments: List<Attachment>) {
fun Context.sendMessageCompat(text: String, addresses: List<String>, subId: Int?, attachments: List<Attachment>, messageId: Long? = null) {
val settings = getSendMessageSettings()
if (subId != null) {
settings.subscriptionId = subId
}
val messagingUtils = messagingUtils
val isMms = attachments.isNotEmpty() || isLongMmsMessage(text, settings) || addresses.size > 1 && settings.group
if (isMms) {
// we send all MMS attachments separately to reduces the chances of hitting provider MMS limit.
if (attachments.size > 1) {
for (i in 0 until attachments.lastIndex) {
val attachment = attachments[i]
messagingUtils.sendMmsMessage("", addresses, listOf(attachment), settings)
if (attachments.isNotEmpty()) {
val lastIndex = attachments.lastIndex
if (attachments.size > 1) {
for (i in 0 until lastIndex) {
val attachment = attachments[i]
messagingUtils.sendMmsMessage("", addresses, attachment, settings, messageId)
}
}
}
val lastAttachment = attachments[attachments.lastIndex]
messagingUtils.sendMmsMessage(text, addresses, listOf(lastAttachment), settings)
val lastAttachment = attachments[lastIndex]
messagingUtils.sendMmsMessage(text, addresses, lastAttachment, settings, messageId)
} else {
messagingUtils.sendMmsMessage(text, addresses, null, settings, messageId)
}
} else {
try {
messagingUtils.sendSmsMessage(text, addresses.toSet(), settings.subscriptionId, settings.deliveryReports)
messagingUtils.sendSmsMessage(text, addresses.toSet(), settings.subscriptionId, settings.deliveryReports, messageId)
} catch (e: SmsException) {
when (e.errorCode) {
EMPTY_DESTINATION_ADDRESS -> toast(id = R.string.empty_destination_address, length = LENGTH_LONG)

View File

@ -31,7 +31,7 @@ class MessagingUtils(val context: Context) {
*/
private fun insertSmsMessage(
subId: Int, dest: String, text: String, timestamp: Long, threadId: Long,
status: Int = Sms.STATUS_NONE, type: Int = Sms.MESSAGE_TYPE_OUTBOX
status: Int = Sms.STATUS_NONE, type: Int = Sms.MESSAGE_TYPE_OUTBOX, messageId: Long? = null
): Uri {
val response: Uri?
val values = ContentValues().apply {
@ -58,7 +58,18 @@ class MessagingUtils(val context: Context) {
}
try {
response = context.contentResolver.insert(Sms.CONTENT_URI, values)
if (messageId != null) {
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(messageId.toString())
val count = context.contentResolver.update(Sms.CONTENT_URI, values, selection, selectionArgs)
if (count > 0) {
response = Uri.parse("${Sms.CONTENT_URI}/${messageId}")
} else {
response = null
}
} else {
response = context.contentResolver.insert(Sms.CONTENT_URI, values)
}
} catch (e: Exception) {
throw SmsException(ERROR_PERSISTING_MESSAGE, e)
}
@ -67,7 +78,7 @@ class MessagingUtils(val context: Context) {
/** Send an SMS message given [text] and [addresses]. A [SmsException] is thrown in case any errors occur. */
fun sendSmsMessage(
text: String, addresses: Set<String>, subId: Int, requireDeliveryReport: Boolean
text: String, addresses: Set<String>, subId: Int, requireDeliveryReport: Boolean, messageId: Long? = null
) {
if (addresses.size > 1) {
// insert a dummy message for this thread if it is a group message
@ -76,7 +87,8 @@ class MessagingUtils(val context: Context) {
insertSmsMessage(
subId = subId, dest = mergedAddresses, text = text,
timestamp = System.currentTimeMillis(), threadId = broadCastThreadId,
status = Sms.Sent.STATUS_COMPLETE, type = Sms.Sent.MESSAGE_TYPE_SENT
status = Sms.Sent.STATUS_COMPLETE, type = Sms.Sent.MESSAGE_TYPE_SENT,
messageId = messageId
)
}
@ -84,7 +96,8 @@ class MessagingUtils(val context: Context) {
val threadId = context.getThreadId(address)
val messageUri = insertSmsMessage(
subId = subId, dest = address, text = text,
timestamp = System.currentTimeMillis(), threadId = threadId
timestamp = System.currentTimeMillis(), threadId = threadId,
messageId = messageId
)
try {
context.smsSender.sendMessage(
@ -133,33 +146,32 @@ class MessagingUtils(val context: Context) {
}
@Deprecated("TODO: Move/rewrite MMS code into the app.")
fun sendMmsMessage(text: String, addresses: List<String>, attachments: List<Attachment>, settings: Settings) {
fun sendMmsMessage(text: String, addresses: List<String>, attachment: Attachment?, settings: Settings, messageId: Long? = null) {
val transaction = Transaction(context, settings)
val message = Message(text, addresses.toTypedArray())
if (attachments.isNotEmpty()) {
for (attachment in attachments) {
try {
val uri = attachment.getUri()
context.contentResolver.openInputStream(uri)?.use {
val bytes = it.readBytes()
val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
"application/txt"
} else {
attachment.mimetype
}
val name = attachment.filename
message.addMedia(bytes, mimeType, name, name)
if (attachment != null) {
try {
val uri = attachment.getUri()
context.contentResolver.openInputStream(uri)?.use {
val bytes = it.readBytes()
val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
"application/txt"
} else {
attachment.mimetype
}
} catch (e: Exception) {
context.showErrorToast(e)
} catch (e: Error) {
context.showErrorToast(e.localizedMessage ?: context.getString(R.string.unknown_error_occurred))
val name = attachment.filename
message.addMedia(bytes, mimeType, name, name)
}
} catch (e: Exception) {
context.showErrorToast(e)
} catch (e: Error) {
context.showErrorToast(e.localizedMessage ?: context.getString(com.simplemobiletools.commons.R.string.unknown_error_occurred))
}
}
val mmsSentIntent = Intent(context, MmsSentReceiver::class.java)
mmsSentIntent.putExtra(MmsSentReceiver.EXTRA_ORIGINAL_RESENT_MESSAGE_ID, messageId)
transaction.setExplicitBroadcastForSentMms(mmsSentIntent)
try {
@ -177,6 +189,7 @@ class MessagingUtils(val context: Context) {
when (resultCode) {
SmsManager.RESULT_ERROR_NO_SERVICE -> context.getString(R.string.error_service_is_unavailable)
SmsManager.RESULT_ERROR_RADIO_OFF -> context.getString(R.string.error_radio_turned_off)
SmsManager.RESULT_NO_DEFAULT_SMS_APP -> context.getString(R.string.sim_card_not_available)
else -> context.getString(R.string.unknown_error_occurred_sending_message, resultCode)
}
}

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.smsmessenger.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "archived_conversations",
indices = [(Index(value = ["thread_id"], unique = true))]
)
data class ArchivedConversation(
@PrimaryKey @ColumnInfo(name = "thread_id") var threadId: Long,
@ColumnInfo(name = "deleted_ts") var deletedTs: Long
)

View File

@ -0,0 +1,13 @@
package com.simplemobiletools.smsmessenger.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
enum class BackupType {
@SerialName("sms")
SMS,
@SerialName("mms")
MMS,
}

View File

@ -16,7 +16,8 @@ data class Conversation(
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
@ColumnInfo(name = "phone_number") var phoneNumber: String,
@ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false,
@ColumnInfo(name = "uses_custom_title") var usesCustomTitle: Boolean = false
@ColumnInfo(name = "uses_custom_title") var usesCustomTitle: Boolean = false,
@ColumnInfo(name = "archived") var isArchived: Boolean = false
) {
companion object {

View File

@ -0,0 +1,16 @@
package com.simplemobiletools.smsmessenger.models
import androidx.room.ColumnInfo
import androidx.room.Embedded
data class ConversationWithSnippetOverride(
@ColumnInfo(name = "new_snippet") val snippet: String?,
@Embedded val conversation: Conversation
) {
fun toConversation() =
if (snippet == null) {
conversation
} else {
conversation.copy(snippet = snippet)
}
}

View File

@ -0,0 +1,5 @@
package com.simplemobiletools.smsmessenger.models
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}

View File

@ -18,6 +18,7 @@ data class Message(
@ColumnInfo(name = "thread_id") val threadId: Long,
@ColumnInfo(name = "is_mms") val isMMS: Boolean,
@ColumnInfo(name = "attachment") val attachment: MessageAttachment?,
@ColumnInfo(name = "sender_phone_number") val senderPhoneNumber: String,
@ColumnInfo(name = "sender_name") var senderName: String,
@ColumnInfo(name = "sender_photo_uri") val senderPhotoUri: String,
@ColumnInfo(name = "subscription_id") var subscriptionId: Int,
@ -28,6 +29,11 @@ data class Message(
fun millis() = date * 1000L
fun getSender(): SimpleContact? =
participants.firstOrNull { it.doesHavePhoneNumber(senderPhoneNumber) }
?: participants.firstOrNull { it.name == senderName }
?: participants.firstOrNull()
companion object {
fun getStableId(message: Message): Long {
@ -37,6 +43,7 @@ data class Message(
result = 31 * result + message.threadId.hashCode()
result = 31 * result + message.isMMS.hashCode()
result = 31 * result + (message.attachment?.hashCode() ?: 0)
result = 31 * result + message.senderPhoneNumber.hashCode()
result = 31 * result + message.senderName.hashCode()
result = 31 * result + message.senderPhotoUri.hashCode()
result = 31 * result + message.isScheduled.hashCode()
@ -53,6 +60,7 @@ data class Message(
old.date == new.date &&
old.isMMS == new.isMMS &&
old.attachment == new.attachment &&
old.senderPhoneNumber == new.senderPhoneNumber &&
old.senderName == new.senderName &&
old.senderPhotoUri == new.senderPhotoUri &&
old.isScheduled == new.isScheduled

View File

@ -0,0 +1,24 @@
package com.simplemobiletools.smsmessenger.models
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.*
@Serializable(with = BackupSerializer::class)
sealed class MessagesBackup() {
@SerialName("backupType")
abstract val backupType: BackupType
}
object BackupSerializer :
JsonContentPolymorphicSerializer<MessagesBackup>(MessagesBackup::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out MessagesBackup> {
return when (element.jsonObject["backupType"]?.jsonPrimitive?.content) {
"sms" -> SmsBackup.serializer()
"mms" -> MmsBackup.serializer()
else -> throw SerializationException("ERROR: No Serializer found. Serialization failed.")
}
}
}

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsAddress(
@SerializedName("address")
val address: String,

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsBackup(
@SerializedName("creator")
val creator: String?,
@ -44,7 +46,9 @@ data class MmsBackup(
val addresses: List<MmsAddress>,
@SerializedName("parts")
val parts: List<MmsPart>,
) {
override val backupType: BackupType = BackupType.MMS,
): MessagesBackup() {
fun toContentValues(): ContentValues {
return contentValuesOf(

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsPart(
@SerializedName("cd")
val contentDisposition: String?,

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.smsmessenger.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "recycle_bin_messages",
indices = [(Index(value = ["id"], unique = true))]
)
data class RecycleBinMessage(
@PrimaryKey val id: Long,
@ColumnInfo(name = "deleted_ts") var deletedTS: Long
)

View File

@ -5,7 +5,9 @@ import android.content.ContentValues
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class SmsBackup(
@SerializedName("sub_id")
val subscriptionId: Long,
@ -28,8 +30,10 @@ data class SmsBackup(
@SerializedName("type")
val type: Int,
@SerializedName("service_center")
val serviceCenter: String?
) {
val serviceCenter: String?,
override val backupType: BackupType = BackupType.SMS,
): MessagesBackup() {
fun toContentValues(): ContentValues {
return contentValuesOf(

View File

@ -2,7 +2,6 @@ package com.simplemobiletools.smsmessenger.models
import android.content.Context
import com.simplemobiletools.commons.extensions.normalizePhoneNumber
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.format
import com.simplemobiletools.smsmessenger.helpers.parseNameFromVCard
@ -38,9 +37,9 @@ data class VCardPropertyWrapper(val value: String, val type: String, val propert
private fun VCardProperty.getPropertyTypeString(context: Context): String {
return when (parameters.type) {
CELL -> context.getString(R.string.mobile)
HOME -> context.getString(R.string.home)
WORK -> context.getString(R.string.work)
CELL -> context.getString(com.simplemobiletools.commons.R.string.mobile)
HOME -> context.getString(com.simplemobiletools.commons.R.string.home)
WORK -> context.getString(com.simplemobiletools.commons.R.string.work)
else -> ""
}
}
@ -50,10 +49,22 @@ data class VCardPropertyWrapper(val value: String, val type: String, val propert
when (this) {
is Telephone -> VCardPropertyWrapper(text.normalizePhoneNumber(), getPropertyTypeString(context), property)
is Email -> VCardPropertyWrapper(value, getPropertyTypeString(context), property)
is Organization -> VCardPropertyWrapper(values.joinToString(), context.getString(R.string.work), property)
is Birthday -> VCardPropertyWrapper(date.format(context.config.dateFormat), context.getString(R.string.birthday), property)
is Anniversary -> VCardPropertyWrapper(date.format(context.config.dateFormat), context.getString(R.string.anniversary), property)
is Note -> VCardPropertyWrapper(value, context.getString(R.string.notes), property)
is Organization -> VCardPropertyWrapper(
value = values.joinToString(),
type = context.getString(com.simplemobiletools.commons.R.string.work),
property = property
)
is Birthday -> VCardPropertyWrapper(
value = date.format(context.config.dateFormat),
type = context.getString(com.simplemobiletools.commons.R.string.birthday),
property = property
)
is Anniversary -> VCardPropertyWrapper(
value = date.format(context.config.dateFormat),
type = context.getString(com.simplemobiletools.commons.R.string.anniversary),
property = property
)
is Note -> VCardPropertyWrapper(value, context.getString(com.simplemobiletools.commons.R.string.notes), property)
else -> VCardPropertyWrapper("", "", property)
}
}

View File

@ -0,0 +1,31 @@
package com.simplemobiletools.smsmessenger.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.deleteMessage
import com.simplemobiletools.smsmessenger.extensions.updateLastConversationMessage
import com.simplemobiletools.smsmessenger.extensions.updateUnreadCountBadge
import com.simplemobiletools.smsmessenger.helpers.IS_MMS
import com.simplemobiletools.smsmessenger.helpers.MESSAGE_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class DeleteSmsReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val threadId = intent.getLongExtra(THREAD_ID, 0L)
val messageId = intent.getLongExtra(MESSAGE_ID, 0L)
val isMms = intent.getBooleanExtra(IS_MMS, false)
context.notificationManager.cancel(threadId.hashCode())
ensureBackgroundThread {
context.deleteMessage(messageId, isMms)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
context.updateLastConversationMessage(threadId)
refreshMessages()
}
}
}

View File

@ -37,11 +37,15 @@ class DirectReplyReceiver : BroadcastReceiver() {
}
ensureBackgroundThread {
var messageId = 0L
try {
context.sendMessageCompat(body, listOf(address), subscriptionId, emptyList())
val message = context.getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false, limit = 1).lastOrNull()
if (message != null) {
context.messagesDB.insertOrUpdate(message)
messageId = message.id
context.updateLastConversationMessage(threadId)
}
} catch (e: Exception) {
context.showErrorToast(e)
@ -50,7 +54,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
val photoUri = SimpleContactsHelper(context).getPhotoUriFromPhoneNumber(address)
val bitmap = context.getNotificationBitmap(photoUri)
Handler(Looper.getMainLooper()).post {
context.notificationHelper.showMessageNotification(address, body, threadId, bitmap, sender = null, alertOnlyOnce = true)
context.notificationHelper.showMessageNotification(messageId, address, body, threadId, bitmap, sender = null, alertOnlyOnce = true)
}
context.markThreadMessagesRead(threadId)

View File

@ -5,15 +5,22 @@ import android.net.Uri
import android.os.Handler
import android.os.Looper
import com.bumptech.glide.Glide
import com.klinker.android.send_message.MmsReceivedReceiver
import com.simplemobiletools.commons.extensions.isNumberBlocked
import com.simplemobiletools.commons.extensions.normalizePhoneNumber
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.getConversations
import com.simplemobiletools.smsmessenger.extensions.getLatestMMS
import com.simplemobiletools.smsmessenger.extensions.insertOrUpdateConversation
import com.simplemobiletools.smsmessenger.extensions.showReceivedMessageNotification
import com.simplemobiletools.smsmessenger.extensions.updateUnreadCountBadge
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
// more info at https://github.com/klinker41/android-smsmms
class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
class MmsReceiver : MmsReceivedReceiver() {
override fun isAddressBlocked(context: Context, address: String): Boolean {
val normalizedAddress = address.normalizePhoneNumber()
@ -22,7 +29,7 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
override fun onMessageReceived(context: Context, messageUri: Uri) {
val mms = context.getLatestMMS() ?: return
val address = mms.participants.firstOrNull()?.phoneNumbers?.first()?.normalizedNumber ?: ""
val address = mms.getSender()?.phoneNumbers?.first()?.normalizedNumber ?: ""
val size = context.resources.getDimension(R.dimen.notification_large_icon_size).toInt()
ensureBackgroundThread {
@ -38,7 +45,7 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
}
Handler(Looper.getMainLooper()).post {
context.showReceivedMessageNotification(address, mms.body, mms.threadId, glideBitmap)
context.showReceivedMessageNotification(mms.id, address, mms.body, mms.threadId, glideBitmap)
val conversation = context.getConversations(mms.threadId).firstOrNull() ?: return@post
ensureBackgroundThread {
context.insertOrUpdateConversation(conversation)
@ -49,5 +56,5 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
}
}
override fun onError(context: Context, error: String) {}
override fun onError(context: Context, error: String) = context.showErrorToast(context.getString(R.string.couldnt_download_mms))
}

View File

@ -11,6 +11,7 @@ import android.widget.Toast
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.deleteMessage
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import java.io.File
@ -19,6 +20,7 @@ class MmsSentReceiver : SendStatusReceiver() {
override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
val uri = Uri.parse(intent.getStringExtra(EXTRA_CONTENT_URI))
val originalResentMessageId = intent.getLongExtra(EXTRA_ORIGINAL_RESENT_MESSAGE_ID, -1L)
val messageBox = if (receiverResultCode == Activity.RESULT_OK) {
Telephony.Mms.MESSAGE_BOX_SENT
} else {
@ -37,6 +39,11 @@ class MmsSentReceiver : SendStatusReceiver() {
context.showErrorToast(e)
}
// In case of resent message, delete original to prevent duplication
if (originalResentMessageId != -1L) {
context.deleteMessage(originalResentMessageId, true)
}
val filePath = intent.getStringExtra(EXTRA_FILE_PATH)
if (filePath != null) {
File(filePath).delete()
@ -50,5 +57,6 @@ class MmsSentReceiver : SendStatusReceiver() {
companion object {
private const val EXTRA_CONTENT_URI = "content_uri"
private const val EXTRA_FILE_PATH = "file_path"
const val EXTRA_ORIGINAL_RESENT_MESSAGE_ID = "original_message_id"
}
}

View File

@ -8,7 +8,6 @@ import android.os.Looper
import android.os.PowerManager
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.deleteScheduledMessage
import com.simplemobiletools.smsmessenger.extensions.getAddresses
@ -56,7 +55,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
} catch (e: Exception) {
context.showErrorToast(e)
} catch (e: Error) {
context.showErrorToast(e.localizedMessage ?: context.getString(R.string.unknown_error_occurred))
context.showErrorToast(e.localizedMessage ?: context.getString(com.simplemobiletools.commons.R.string.unknown_error_occurred))
}
}
}

View File

@ -55,8 +55,21 @@ class SmsReceiver : BroadcastReceiver() {
}
private fun handleMessage(
context: Context, address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int, status: Int
context: Context,
address: String,
subject: String,
body: String,
date: Long,
read: Int,
threadId: Long,
type: Int,
subscriptionId: Int,
status: Int
) {
if (isMessageFilteredOut(context, body)) {
return
}
val photoUri = SimpleContactsHelper(context).getPhotoUriFromPhoneNumber(address)
val bitmap = context.getNotificationBitmap(photoUri)
Handler(Looper.getMainLooper()).post {
@ -83,13 +96,40 @@ class SmsReceiver : BroadcastReceiver() {
val messageDate = (date / 1000).toInt()
val message =
Message(newMessageId, body, type, status, participants, messageDate, false, threadId, false, null, senderName, photoUri, subscriptionId)
Message(
newMessageId,
body,
type,
status,
participants,
messageDate,
false,
threadId,
false,
null,
address,
senderName,
photoUri,
subscriptionId
)
context.messagesDB.insertOrUpdate(message)
if (context.config.isArchiveAvailable) {
context.updateConversationArchivedStatus(threadId, false)
}
refreshMessages()
context.showReceivedMessageNotification(newMessageId, address, body, threadId, bitmap)
}
context.showReceivedMessageNotification(address, body, threadId, bitmap)
}
}
}
private fun isMessageFilteredOut(context: Context, body: String): Boolean {
for (blockedKeyword in context.config.blockedKeywords) {
if (body.contains(blockedKeyword, ignoreCase = true)) {
return true
}
}
return false
}
}

View File

@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.55,5.22l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6C5.53,3 5.12,3.21 4.85,3.55L3.46,5.22C3.17,5.57 3,6.01 3,6.5V19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5C21,6.01 20.83,5.57 20.55,5.22zM12,9.5l5.5,5.5H14v2h-4v-2H6.5L12,9.5zM5.12,5l0.82,-1h12l0.93,1H5.12z"/>
</vector>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/archive_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/archive_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/color_primary"
app:title="@string/archived_conversations"
app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" />
<RelativeLayout
android:id="@+id/archive_nested_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
android:fillViewport="true"
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/archive_coordinator_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/archive_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/conversations_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:showAnimationBehavior="inward"
app:showDelay="250"
tools:visibility="visible" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/no_conversations_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/bigger_margin"
android:alpha="0.8"
android:gravity="center"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"
android:text="@string/no_archived_conversations"
android:textSize="@dimen/bigger_text_size"
android:textStyle="italic"
android:visibility="gone" />
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
android:id="@+id/conversations_fastscroller"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/conversations_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:layoutAnimation="@anim/layout_animation"
android:overScrollMode="ifContentScrolls"
android:scrollbars="none"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" />
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/block_keywords_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/block_keywords_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/color_primary"
app:menu="@menu/menu_add_blocked_keyword"
app:title="@string/manage_blocked_keywords"
app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manage_blocked_keywords_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/manage_blocked_keywords_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_manage_blocked_keyword" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_keywords_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:gravity="center_horizontal"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:text="@string/not_blocking_keywords"
android:textSize="@dimen/bigger_text_size"
android:textStyle="italic"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_keywords_placeholder_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ripple_all_corners"
android:gravity="center"
android:padding="@dimen/activity_margin"
android:text="@string/add_a_blocked_keyword"
android:textSize="@dimen/bigger_text_size"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manage_blocked_keywords_placeholder" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/recycle_bin_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/recycle_bin_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/color_primary"
app:title="@string/recycle_bin"
app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" />
<RelativeLayout
android:id="@+id/recycle_bin_nested_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
android:fillViewport="true"
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/recycle_bin_coordinator_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/recycle_bin_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/conversations_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:showAnimationBehavior="inward"
app:showDelay="250"
tools:visibility="visible" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/no_conversations_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/bigger_margin"
android:alpha="0.8"
android:gravity="center"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"
android:text="@string/no_conversations_found"
android:textSize="@dimen/bigger_text_size"
android:textStyle="italic"
android:visibility="gone" />
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
android:id="@+id/conversations_fastscroller"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/conversations_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:layoutAnimation="@anim/layout_animation"
android:overScrollMode="ifContentScrolls"
android:scrollbars="none"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" />
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -146,6 +146,21 @@
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_manage_blocked_keywords_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_manage_blocked_keywords"
style="@style/SettingsTextLabelStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manage_blocked_keywords" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_font_size_holder"
style="@style/SettingsHolderTextViewStyle"
@ -341,6 +356,122 @@
tools:text="@string/mms_file_size_limit_none" />
</RelativeLayout>
<include
android:id="@+id/settings_outgoing_messages_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_recycle_bin_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/recycle_bin" />
<RelativeLayout
android:id="@+id/settings_use_recycle_bin_holder"
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_use_recycle_bin"
style="@style/SettingsCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/move_items_into_recycle_bin" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_empty_recycle_bin_holder"
style="@style/SettingsHolderTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_empty_recycle_bin_label"
style="@style/SettingsTextLabelStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_recycle_bin" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_empty_recycle_bin_size"
style="@style/SettingsTextValueStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/settings_empty_recycle_bin_label"
tools:text="0 B" />
</RelativeLayout>
<include
android:id="@+id/settings_recycle_bin_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_security_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/security" />
<RelativeLayout
android:id="@+id/settings_app_password_protection_holder"
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_app_password_protection"
style="@style/SettingsCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_protect_whole_app" />
</RelativeLayout>
<include
android:id="@+id/settings_migrating_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_migrating_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/migrating" />
<RelativeLayout
android:id="@+id/settings_export_messages_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_export_messages"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/export_messages" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_import_messages_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_import_messages"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/import_messages" />
</RelativeLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -102,7 +102,7 @@
android:id="@+id/thread_messages_fastscroller"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/reply_disabled_info_holder"
app:layout_constraintBottom_toTopOf="@id/short_code_holder"
app:layout_constraintTop_toBottomOf="@id/thread_add_contacts"
app:supportSwipeToRefresh="true">
@ -117,7 +117,7 @@
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"
app:stackFromEnd="true"
tools:itemCount="3"
tools:listitem="@layout/item_sent_message" />
tools:listitem="@layout/item_message" />
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
@ -134,17 +134,19 @@
tools:ignore="ContentDescription" />
<include
android:id="@+id/short_code_holder"
layout="@layout/layout_invalid_short_code_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/thread_send_message_holder"
app:layout_constraintBottom_toTopOf="@id/message_holder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/thread_messages_fastscroller"
tools:visibility="visible" />
<include
android:id="@+id/message_holder"
layout="@layout/layout_thread_send_message_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/add_blocked_keyword_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:hint="@string/keyword">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_blocked_keyword_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textCursorDrawable="@null"
android:textSize="@dimen/bigger_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
</RelativeLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/delete_remember_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/big_margin"
android:paddingTop="@dimen/big_margin"
android:paddingRight="@dimen/big_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/delete_remember_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/small_margin"
android:paddingBottom="@dimen/activity_margin"
android:text="@string/delete_whole_conversation_confirmation"
android:textSize="@dimen/bigger_text_size" />
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/skip_the_recycle_bin_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/delete_remember_title"
android:text="@string/skip_the_recycle_bin_messages" />
</RelativeLayout>

View File

@ -14,21 +14,6 @@
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/export_messages_folder_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:hint="@string/folder">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/export_messages_folder"
style="@style/UnclickableEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/export_messages_filename_hint"
android:layout_width="match_parent"

View File

@ -17,7 +17,6 @@
android:id="@+id/import_sms_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingBottom="@dimen/small_margin"
android:text="@string/import_sms" />
@ -26,7 +25,6 @@
android:id="@+id/import_mms_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingBottom="@dimen/small_margin"
android:text="@string/import_mms" />

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.simplemobiletools.commons.views.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialog_message_details_text_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/big_margin"
android:paddingTop="@dimen/big_margin"
android:paddingEnd="@dimen/big_margin"
android:textIsSelectable="true"
android:textSize="@dimen/big_text_size"
tools:text="My sample text" />

View File

@ -6,6 +6,7 @@
android:layout_height="wrap_content">
<include
android:id="@+id/document_attachment_holder"
layout="@layout/item_attachment_document"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -19,6 +20,7 @@
app:layout_constraintWidth_max="@dimen/attachment_preview_max_width" />
<include
android:id="@+id/remove_attachment_button_holder"
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -44,6 +44,7 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include
android:id="@+id/remove_attachment_button_holder"
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -6,6 +6,7 @@
android:layout_height="wrap_content">
<include
android:id="@+id/vcard_attachment_holder"
layout="@layout/item_attachment_vcard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -28,6 +29,7 @@
app:layout_constraintTop_toTopOf="@id/vcard_attachment_holder" />
<include
android:id="@+id/remove_attachment_button_holder"
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -4,10 +4,9 @@
android:id="@+id/conversation_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:layout_marginBottom="@dimen/tiny_margin"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selector">
android:focusable="true">
<RelativeLayout
android:id="@+id/conversation_holder"

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/manage_blocked_keyword_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/tiny_margin"
android:clickable="true"
android:focusable="true">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_keyword_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:layout_toStartOf="@+id/overflow_menu_icon" />
<ImageView
android:id="@+id/overflow_menu_icon"
style="@style/OverflowMenuIconStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
<View
android:id="@+id/overflow_menu_anchor"
style="@style/OverflowMenuAnchorStyle"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
</RelativeLayout>

View File

@ -5,11 +5,9 @@
android:id="@+id/thread_message_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginBottom="@dimen/medium_margin"
android:layout_marginTop="@dimen/small_margin"
android:foreground="@drawable/selector"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin">
android:paddingHorizontal="@dimen/activity_margin">
<RelativeLayout
android:id="@+id/thread_message_wrapper"
@ -27,13 +25,14 @@
android:layout_alignBottom="@+id/thread_message_body"
android:layout_alignParentStart="true"
android:layout_marginEnd="@dimen/medium_margin"
android:visibility="gone" />
android:src="@drawable/ic_person_vector"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/thread_mesage_attachments_holder"
android:id="@+id/thread_message_attachments_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/thread_message_sender_photo"
android:divider="@drawable/linear_layout_vertical_divider"
android:orientation="vertical"
android:showDividers="middle" />
@ -42,23 +41,26 @@
android:id="@+id/thread_message_play_outline"
android:layout_width="@dimen/play_outline_size"
android:layout_height="@dimen/play_outline_size"
android:layout_alignEnd="@+id/thread_mesage_attachments_holder"
android:layout_alignBottom="@+id/thread_mesage_attachments_holder"
android:layout_marginStart="@dimen/medium_margin"
android:layout_alignEnd="@+id/thread_message_attachments_holder"
android:layout_alignBottom="@+id/thread_message_attachments_holder"
android:layout_marginEnd="@dimen/medium_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:src="@drawable/ic_play_outline_vector"
android:visibility="gone" />
<TextView
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/thread_message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/thread_mesage_attachments_holder"
android:layout_toEndOf="@+id/thread_message_sender_photo"
android:layout_below="@+id/thread_message_attachments_holder"
android:layout_marginVertical="@dimen/tiny_margin"
android:layout_toEndOf="@id/thread_message_sender_photo"
android:autoLink="email|web"
android:background="@drawable/item_received_background"
android:drawablePadding="8dp"
android:padding="@dimen/normal_margin"
android:textSize="@dimen/normal_text_size"
tools:text="Received message" />
tools:drawableEndCompat="@drawable/scheduled_message_icon"
tools:text="Message content" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/thread_message_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/small_margin"
android:foreground="@drawable/selector"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin">
<RelativeLayout
android:id="@+id/thread_message_wrapper"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.8">
<LinearLayout
android:id="@+id/thread_mesage_attachments_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/tiny_margin"
android:divider="@drawable/linear_layout_vertical_divider"
android:orientation="vertical"
android:showDividers="middle" />
<ImageView
android:id="@+id/thread_message_play_outline"
android:layout_width="@dimen/play_outline_size"
android:layout_height="@dimen/play_outline_size"
android:layout_alignEnd="@+id/thread_mesage_attachments_holder"
android:layout_alignBottom="@+id/thread_mesage_attachments_holder"
android:layout_marginEnd="@dimen/medium_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:src="@drawable/ic_play_outline_vector"
android:visibility="gone" />
<TextView
android:id="@+id/thread_message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/thread_mesage_attachments_holder"
android:layout_alignParentEnd="true"
android:layout_marginVertical="@dimen/tiny_margin"
android:autoLink="email|web"
android:background="@drawable/item_sent_background"
android:padding="@dimen/normal_margin"
android:textSize="@dimen/normal_text_size"
tools:text="Sent message" />
</RelativeLayout>
<ImageView
android:id="@+id/thread_message_scheduled_icon"
android:layout_width="@dimen/small_icon_size"
android:layout_height="@dimen/small_icon_size"
android:layout_margin="@dimen/tiny_margin"
android:src="@drawable/ic_clock_vector"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reply_disabled_info_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"

View File

@ -2,7 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/thread_send_message_holder"
android:id="@+id/message_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -29,7 +29,9 @@
android:layout_height="wrap_content"
android:layout_alignTop="@+id/scheduled_message_button"
android:layout_alignBottom="@+id/scheduled_message_button"
android:paddingStart="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/small_margin"
android:paddingBottom="@dimen/medium_margin"
android:src="@drawable/ic_clock_vector" />
@ -202,6 +204,7 @@
tools:visibility="visible">
<include
android:id="@+id/attachment_picker"
layout="@layout/layout_attachment_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource,AlwaysShowAction">
<item
android:id="@+id/empty_archive"
android:showAsAction="never"
android:title="@string/empty_archive"
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource,AlwaysShowAction">
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector"
android:showAsAction="always"
android:title="@string/delete"
app:showAsAction="always" />
<item
android:id="@+id/cab_unarchive"
android:icon="@drawable/ic_unarchive_vector"
android:showAsAction="ifRoom"
android:title="@string/unarchive"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_select_all"
android:icon="@drawable/ic_select_all_vector"
android:title="@string/select_all"
app:showAsAction="ifRoom" />
</menu>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/cab_copy_keyword"
android:icon="@drawable/ic_copy_vector"
android:title="@string/copy_to_clipboard"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector"
android:title="@string/delete"
app:showAsAction="ifRoom" />
</menu>

View File

@ -26,6 +26,12 @@
android:icon="@drawable/ic_minus_circle_vector"
android:title="@string/block_number"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_archive"
android:icon="@drawable/ic_archive_vector"
android:showAsAction="ifRoom"
android:title="@string/archive"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_copy_number"
android:showAsAction="never"

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource,AlwaysShowAction">
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector"
android:showAsAction="always"
android:title="@string/delete"
app:showAsAction="always" />
<item
android:id="@+id/cab_restore"
android:showAsAction="never"
android:title="@string/restore_all_messages"
app:showAsAction="never" />
<item
android:id="@+id/cab_select_all"
android:icon="@drawable/ic_select_all_vector"
android:title="@string/select_all"
app:showAsAction="ifRoom" />
</menu>

View File

@ -26,6 +26,16 @@
android:icon="@drawable/ic_save_vector"
android:title="@string/save_as"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_properties"
android:icon="@drawable/ic_info_vector"
android:title="@string/properties"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_restore"
android:showAsAction="never"
android:title="@string/restore"
app:showAsAction="never" />
<item
android:id="@+id/cab_forward_message"
android:showAsAction="never"

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_blocked_keyword"
android:icon="@drawable/ic_plus_vector"
android:title="@string/add_a_blocked_keyword"
app:showAsAction="ifRoom" />
</menu>

View File

@ -4,14 +4,14 @@
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource,AlwaysShowAction">
<item
android:id="@+id/import_messages"
android:id="@+id/show_recycle_bin"
android:showAsAction="never"
android:title="@string/import_messages"
android:title="@string/show_the_recycle_bin"
app:showAsAction="never" />
<item
android:id="@+id/export_messages"
android:id="@+id/show_archived"
android:showAsAction="never"
android:title="@string/export_messages"
android:title="@string/show_archived_conversations"
app:showAsAction="never" />
<item
android:id="@+id/settings"

View File

@ -23,6 +23,16 @@
android:icon="@drawable/ic_edit_vector"
android:title="@string/rename_conversation"
app:showAsAction="always" />
<item
android:id="@+id/archive"
android:icon="@drawable/ic_archive_vector"
android:title="@string/archive"
app:showAsAction="ifRoom" />
<item
android:id="@+id/unarchive"
android:icon="@drawable/ic_unarchive_vector"
android:title="@string/unarchive"
app:showAsAction="ifRoom" />
<item
android:id="@+id/conversation_details"
android:icon="@drawable/ic_info_vector"
@ -37,6 +47,11 @@
android:showAsAction="never"
android:title="@string/block_number"
app:showAsAction="never" />
<item
android:id="@+id/restore"
android:showAsAction="never"
android:title="@string/restore_all_messages"
app:showAsAction="never" />
<item
android:id="@+id/mark_as_unread"
android:showAsAction="never"

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:ignore="AppCompatResource">
<item
android:id="@+id/empty_recycle_bin"
android:icon="@drawable/ic_delete_vector"
android:showAsAction="ifRoom"
android:title="@string/empty_recycle_bin"
app:showAsAction="ifRoom" />
</menu>

Some files were not shown because too many files have changed in this diff Show More