Compare commits
364 Commits
Author | SHA1 | Date |
---|---|---|
tibbi | 8b27d9f379 | |
tibbi | 11ded3f6e9 | |
Tibor Kaputa | 7341cfdf20 | |
Naveen | 3d6ab4c602 | |
Naveen | 5b3b58aedc | |
Tibor Kaputa | 96749ac330 | |
Naveen | 30ba6dce5b | |
Tibor Kaputa | fe0553650e | |
tibbi | f5ce526ff6 | |
tibbi | d79c9a0ba9 | |
tibbi | 52a0a7fca6 | |
Tibor Kaputa | 9987058f7d | |
Tibor Kaputa | 9c1a348e74 | |
Nguyễn Hoàng Minh | b8b2313e88 | |
Anonymous | b913b475da | |
Nguyễn Hoàng Minh | 6f4909cd4a | |
Tibor Kaputa | 1d804ebe24 | |
Ensar Sarajčić | 625b515064 | |
Ensar Sarajčić | a31af991c5 | |
Ensar Sarajčić | e41630e543 | |
Ensar Sarajčić | 0e68da2710 | |
tibbi | 30b54076ce | |
Tibor Kaputa | e735502700 | |
Naveen | 4582bc5289 | |
Tibor Kaputa | 67e9b4587b | |
Lionel HANNEQUIN | 6fe03875fe | |
gallegonovato | d707589076 | |
Lionel HANNEQUIN | 77ab71885a | |
Puppelimies | 2059537d44 | |
Tibor Kaputa | 67ae69caf1 | |
Puppelimies | 4463d6b697 | |
gallegonovato | 390a9834e0 | |
gallegonovato | a0b7a93413 | |
Peter Dave Hello | 580ae99eef | |
gallegonovato | 7a4fcd8d1a | |
tibbi | 6ecc87c433 | |
tibbi | a5384af91f | |
tibbi | e2a8b69a8d | |
Tibor Kaputa | c1841a8a11 | |
Naveen | 50198b1898 | |
Naveen | 3ffc6874cb | |
Naveen | b63d904dfe | |
Naveen | a87f33f88f | |
tibbi | e255d7284b | |
tibbi | d5bdb11011 | |
tibbi | a2a68113f5 | |
tibbi | 2719605ce8 | |
tibbi | 66bf43f653 | |
tibbi | a62c88a96b | |
tibbi | aa478f0463 | |
Tibor Kaputa | dc7542d8c1 | |
elgratea | f645f06403 | |
J. Lavoie | 74710ca692 | |
Lionel HANNEQUIN | 8bbfa720da | |
Tibor Kaputa | 0bf9938328 | |
tibbi | a2d60b2a2b | |
fatih ergin | c65755ee24 | |
fatih ergin | 912fff1f43 | |
tibbi | b6388bfe9c | |
Tibor Kaputa | 6ba24c1b29 | |
Ensar Sarajčić | 2d14709169 | |
tibbi | e555cf699d | |
tibbi | 1d370269c1 | |
Tibor Kaputa | 3b9d5c4e50 | |
Tibor Kaputa | a58b839a0d | |
Sergio Marques | 8a0fa650bd | |
en2sv | e5e00b0462 | |
Priit Jõerüüt | 76bba30faf | |
Komjaunietis Latvijas | 78b9407820 | |
Usland | 72923362ee | |
Tibor Kaputa | 582115fbe8 | |
Naveen | dd563774ee | |
Tibor Kaputa | 2b876f7a6f | |
spkprs | 1d7c73ede8 | |
Tibor Kaputa | 73fbbf3289 | |
Josep M. Ferrer | 450fd1c4a7 | |
Tibor Kaputa | 6f84ad0bfe | |
Oğuz Ersen | 1616cae2af | |
Rex_sa | 37add6e176 | |
Eric | 0cc60f9649 | |
solokot | a0e61dc844 | |
Agnieszka C | 1ce1eed123 | |
Guillaume | 8a106c3474 | |
Guillaume | 7d48a02313 | |
VfBFan | 954e66e83e | |
Tibor Kaputa | a14ba9430a | |
elgratea | 05e226e9c0 | |
Tibor Kaputa | 43ee2a99ba | |
Naveen | db3493c146 | |
Naveen | 9ea707d2ba | |
Tibor Kaputa | bb7869b5a8 | |
Tibor Kaputa | 0c976f8f3f | |
J. Lavoie | fdeff6daa1 | |
J. Lavoie | d5dc1dba88 | |
Slávek Banko | 2dc672ed56 | |
Tibor Kaputa | aa8d1628ae | |
Naveen | 69ff590cf0 | |
Naveen | e76130d037 | |
Tibor Kaputa | 166998f86e | |
Naveen | 1089d838db | |
Tibor Kaputa | 7135ca44bf | |
Naveen | c3fe4d8aca | |
Naveen | cdcc9e2140 | |
Naveen | 66be224b38 | |
Naveen | 8bc6659d71 | |
Naveen | fead93ddf7 | |
Naveen | 8ebcaa3016 | |
Naveen | 71e0c177e1 | |
Naveen | dccbc6ce7a | |
Naveen | c4b1e5b6b3 | |
Naveen | dae74d5ded | |
Naveen | c7a9f44663 | |
Naveen Singh | e27e33b323 | |
Naveen | 0c01e607bb | |
Naveen | 3e1675d579 | |
Tibor Kaputa | b2696c56a3 | |
Tibor Kaputa | 7d57b9a9a9 | |
Naveen | 496ff77532 | |
en2sv | ac266db6c0 | |
Milan Šalka | 494624b0c3 | |
Agnieszka C | cca6f90cfa | |
Gabriel Camargo | ab369f515d | |
cwpute | d972a494d2 | |
Tibor Kaputa | 8a933a2e44 | |
solokot | dcb9bbcc4d | |
Tibor Kaputa | 823c6883dc | |
Josep M. Ferrer | e77c6b5da4 | |
Priit Jõerüüt | 6f937ef167 | |
Rex_sa | d65c7f0689 | |
Eric | 98cb5518a1 | |
Oğuz Ersen | 97fcab0123 | |
Agnieszka C | 5a1aec1cdd | |
Guillaume | 6b11bbd408 | |
VfBFan | 3c88dcf8f6 | |
Tibor Kaputa | f17f32cd62 | |
spkprs | c1575ef821 | |
Tibor Kaputa | 23c4229eb0 | |
Tibor Kaputa | a16f37b12a | |
Tibor Kaputa | c3ed0dfddf | |
en2sv | e1f999f26b | |
Ensar Sarajčić | b85661eb4d | |
Ensar Sarajčić | 24756285cc | |
Ensar Sarajčić | 3cb3e24e2c | |
Ensar Sarajčić | 0ec0b89cea | |
Ensar Sarajčić | 6ca6462155 | |
Ensar Sarajčić | 450a0c22d1 | |
Tibor Kaputa | e580279890 | |
en2sv | fb1b784a80 | |
Sergio Marques | 461b1efbde | |
Josep M. Ferrer | 94ef53eba2 | |
solokot | 8d2b93793a | |
Tibor Kaputa | 96c8b4b1ec | |
Ensar Sarajčić | 0a19596053 | |
Ensar Sarajčić | dbf582b239 | |
Ensar Sarajčić | 312f5bd0a8 | |
Tibor Kaputa | de695a5a62 | |
Tibor Kaputa | deecd78650 | |
merkost | 014ac2a4cf | |
Tibor Kaputa | f875688bf3 | |
solokot | dc09352213 | |
Priit Jõerüüt | d0fc054774 | |
VfBFan | 159e73ecc9 | |
Priit Jõerüüt | 264745c216 | |
Rex_sa | 543050b9d4 | |
Eric | e09de4d334 | |
Oğuz Ersen | b92d85a0b6 | |
Agnieszka C | 661293be41 | |
Guillaume | 690d4aca30 | |
VfBFan | dac1bced86 | |
abc0922001 | 59b927333b | |
VfBFan | 68d88e97d0 | |
VfBFan | b135263fa6 | |
Tibor Kaputa | edc71fb861 | |
merkost | e244fd5a53 | |
spkprs | 3d95ce2b83 | |
merkost | 39ca2d6079 | |
merkost | 8c0508b0c1 | |
merkost | 842368d0f4 | |
merkost | c72dc199aa | |
Ensar Sarajčić | bcb42d0ff5 | |
Ensar Sarajčić | cc6e9358f6 | |
Ensar Sarajčić | 565f991932 | |
Ensar Sarajčić | 3f06b521bf | |
Ensar Sarajčić | 31be5d3d95 | |
Ensar Sarajčić | 372dbaeaa4 | |
Ensar Sarajčić | b29d664dc4 | |
merkost | ec6bf55025 | |
merkost | 05ced83909 | |
tibbi | fd65d26f8f | |
Tibor Kaputa | 0c5242df2d | |
Ensar Sarajčić | e86e089dc5 | |
Tibor Kaputa | b9f956f7e8 | |
Tibor Kaputa | 9208eedf6b | |
Ensar Sarajčić | a07ac2c8e6 | |
Ensar Sarajčić | a3d723835c | |
Ensar Sarajčić | e07fbe40a6 | |
Ensar Sarajčić | 5dff8367e3 | |
Ensar Sarajčić | b9b85ea6a7 | |
Tibor Kaputa | 4501e2fe6b | |
Wilson | f6b5bbf455 | |
Ensar Sarajčić | b0141fe93d | |
Ensar Sarajčić | d560720ac3 | |
Ensar Sarajčić | 555b6ebea3 | |
merkost | db5decfcd8 | |
Konstantin Merenkov | 321e4f11ff | |
merkost | 47866a1c19 | |
merkost | 679236e3fa | |
merkost | 4d378e819c | |
merkost | 30b100b62f | |
merkost | 5a8cc0f14d | |
Ensar Sarajčić | 1b8cfee9ea | |
merkost | 5363af1071 | |
merkost | 56ce7c5aa4 | |
Ensar Sarajčić | 857a4f0b93 | |
Tibor Kaputa | 53aa4495a0 | |
Ensar Sarajčić | 222b96e8c5 | |
Tibor Kaputa | ea4d67c1df | |
solokot | 5a0e9d26fc | |
Tibor Kaputa | 1995da2916 | |
Ensar Sarajčić | c79242f571 | |
Ensar Sarajčić | a35edce84a | |
Ensar Sarajčić | a6b97698bf | |
Ensar Sarajčić | 0ad178fddb | |
Tibor Kaputa | a90e555186 | |
elgratea | 43c4c1395c | |
Josep M. Ferrer | ec65179a77 | |
Rex_sa | bce96691a3 | |
Eric | e9ff8bf323 | |
Oğuz Ersen | ba88dc8dce | |
Guillaume | 2c2c629a5c | |
Tibor Kaputa | c5cf200fd0 | |
spkprs | 2a5eff964c | |
tibbi | 17f1bf62b1 | |
Tibor Kaputa | eb35bf3761 | |
Ensar Sarajčić | ca66206034 | |
Ensar Sarajčić | 4d13fa1079 | |
Ensar Sarajčić | c1446194a1 | |
Ensar Sarajčić | 3fc1ddbff9 | |
Tibor Kaputa | 8ebef8801d | |
Ensar Sarajčić | 1b7874446b | |
Ensar Sarajčić | c51dc0b935 | |
Tibor Kaputa | 912776035d | |
Agnieszka C | 512a354f44 | |
Josep M. Ferrer | 955374b470 | |
Rex_sa | 8ec167da4e | |
Eric | 2d6f05f0f5 | |
Oğuz Ersen | 705181e076 | |
solokot | 0c4774cd56 | |
Agnieszka C | 52fecff926 | |
Guillaume | d15384db26 | |
Ensar Sarajčić | ab898bfcbe | |
Ensar Sarajčić | d97a6f6a5f | |
Ensar Sarajčić | 4b3fa422be | |
Ensar Sarajčić | 0e2dd357d1 | |
Ensar Sarajčić | e825e44f54 | |
Ensar Sarajčić | 7dbd6c5d9f | |
Ensar Sarajčić | 47861f605d | |
Tibor Kaputa | 7ca11c8427 | |
Tibor Kaputa | aecaff6d6a | |
Ensar Sarajčić | cf7003e3b4 | |
Ensar Sarajčić | 98eb62e1b6 | |
Ensar Sarajčić | d27a2f5747 | |
Ensar Sarajčić | 3adfdd401e | |
Ensar Sarajčić | 6fef121599 | |
Ensar Sarajčić | a5d6e7724c | |
Tibor Kaputa | 1aa9a3a1a5 | |
Tibor Kaputa | b49643562b | |
Ensar Sarajčić | daea2d2e5d | |
Ensar Sarajčić | c1b29646d3 | |
Ensar Sarajčić | 45416c07bd | |
Ensar Sarajčić | 28a19a09ce | |
Ensar Sarajčić | e4269c8356 | |
Ensar Sarajčić | bdd506c96e | |
Ensar Sarajčić | 674a467694 | |
Tibor Kaputa | 9942fb788a | |
Wilson | e3bf8541df | |
Tibor Kaputa | 6540fe2aa7 | |
Ensar Sarajčić | daf11dc6fd | |
Tibor Kaputa | b0da7bad31 | |
Ensar Sarajčić | 9ff6c3cbb6 | |
Ensar Sarajčić | 8675de70c2 | |
Ensar Sarajčić | ae2e480876 | |
Ensar Sarajčić | ce76573614 | |
Ensar Sarajčić | 143aaece4b | |
Tibor Kaputa | af9143f46e | |
J. Lavoie | 4bacfa4fa8 | |
J. Lavoie | f8982156fa | |
Tibor Kaputa | 6dc79837d3 | |
Tibor Kaputa | 73c12d0db1 | |
Alier | 3a4f54b4be | |
Alier | a21662baef | |
AAlier | 5169539a88 | |
Alier | b076da3840 | |
tibbi | 220dec1cd5 | |
tibbi | 77768c66e0 | |
tibbi | 4dfdb39603 | |
tibbi | 1d2443fda3 | |
tibbi | e8d021a662 | |
Tibor Kaputa | 32f6307464 | |
Milo Ivir | 08dd629b8b | |
Kryštof Černý | ba518ea641 | |
gallegonovato | 80428e2658 | |
Tibor Kaputa | 063cd2bace | |
Tibor Kaputa | 8d260d0b2e | |
en2sv | 3074f28279 | |
Agnieszka C | bb61229cef | |
tibbi | d168889767 | |
tibbi | 2bba900eb1 | |
tibbi | 16734aa93d | |
tibbi | 3dadb0ccb8 | |
tibbi | fb96109973 | |
Tibor Kaputa | dd48fa544e | |
Tibor Kaputa | 6b7e8faefe | |
Milo Ivir | fcfd0574bb | |
Priit Jõerüüt | e7bc2c5e6d | |
Tibor Kaputa | 816d15b999 | |
Tibor Kaputa | e475069c42 | |
Kevin Cotugno | da117ecca4 | |
Tibor Kaputa | abedc2a18a | |
Tibor Kaputa | bfadbb42b5 | |
Tibor Kaputa | eb960c2a34 | |
spkprs | ccb61683ef | |
Sergio Marques | dd02f4cf61 | |
Tibor Kaputa | 90ff57d877 | |
Josep M. Ferrer | a606058ad6 | |
Rex_sa | 1abe0b84ae | |
Eric | 83397c508b | |
Oğuz Ersen | 0970c478eb | |
solokot | ff9a96f32c | |
Agnieszka C | eabacec0b1 | |
Guillaume | c66bdbf6c5 | |
VfBFan | 05e4d8e42b | |
yparitcher | 707d7499e3 | |
tibbi | 4596ab4475 | |
tibbi | 69e448f0a1 | |
tibbi | 83c56c9f00 | |
Tibor Kaputa | 055c1c7c08 | |
Tibor Kaputa | 616cab35f3 | |
yparitcher | 7b5d32e624 | |
FTno | a57f8de092 | |
FTno | 4ca0144219 | |
en2sv | e756fbf9c3 | |
Slávek Banko | 4f140c5754 | |
tibbi | a54155d358 | |
Ryan Mullin | 41083ef754 | |
tibbi | 66c8b21cce | |
Anis-cpu-13 | b4ea472884 | |
tibbi | fb0da99dc0 | |
tibbi | 06b435eae6 | |
tibbi | 197da1c7c9 | |
tibbi | c3e9108132 | |
tibbi | 2ff719d2ac | |
Tibor Kaputa | d6f8d58867 | |
tibbi | 587250fd8d | |
Tibor Kaputa | 8eb5d7e7af | |
Naveen Singh | 8fb6f9ca83 | |
Tibor Kaputa | 2293a76045 | |
Kevin Cotugno | cdbb16bdc8 | |
Milo Ivir | 3f6f301dca | |
P.O | d686b18ae5 | |
Eric | 160ac78220 | |
J. Lavoie | 4ab858f652 | |
J. Lavoie | 0198c14e82 | |
Naveen | abb7e66105 |
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -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)*
|
||||
----------------------------
|
||||
|
||||
|
|
|
@ -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%">
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 { *; }
|
||||
|
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()}")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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,
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.simplemobiletools.smsmessenger.models
|
||||
|
||||
enum class ImportResult {
|
||||
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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?,
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue