mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-08 09:18:39 +01:00
Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 671dfceac3 | |||
| 5626fb74ae | |||
| 88d6c91517 | |||
| aa76cefb1c | |||
| a4fde60c1c | |||
| 8e86612fc2 | |||
| f6ded23383 | |||
| 2ab689c59b | |||
| 155f6a88f8 | |||
| 3d84fc9c98 | |||
| 976e146248 | |||
| d9b0723194 | |||
| 0630a6910a | |||
| bc930345b9 | |||
| d7d7963101 | |||
| fa2551dfcf | |||
| b260a47b49 | |||
| 47e55fc621 | |||
| 700fe5e463 | |||
| ca3d239ce2 | |||
| 12385b9c5a | |||
| 65b26adb3d | |||
| 31b8927291 | |||
| 94ffbb3e8e | |||
| 1b7616b4db | |||
| 71850e1e35 | |||
| 380fb60791 | |||
| 2cf9fa0524 | |||
| c8c4fdc65e | |||
| 9fb16be74f | |||
| 2d4d4aab66 | |||
| f18070b78c | |||
| adf1d8a43a | |||
| f360934ddd | |||
| b986c4d54c | |||
| 4545e70384 | |||
| 3c5cbc3114 | |||
| 879b0ad95d | |||
| 9982810edb | |||
| 699b788187 | |||
| bd61044643 | |||
| 6804d58323 | |||
| bfe57b4b8f | |||
| e3fe852a34 | |||
| 673fe2625c | |||
| e798feb1d7 | |||
| fa937f9f43 | |||
| ec5f3fc333 | |||
| d85c150a8c | |||
| a5faf0e098 | |||
| 1234c63836 | |||
| 91920319c7 | |||
| 950d9d5a4c | |||
| 467dae8132 | |||
| d1ef9d5dcf | |||
| a60419a442 | |||
| f97a034c34 | |||
| 4d0fbe2343 | |||
| 1d5e108cd4 | |||
| 716afc98ac | |||
| 459607adae | |||
| 784b705265 | |||
| 326b95bd10 | |||
| 466acaf504 | |||
| 838165c3e6 | |||
| e1d7ad7d03 | |||
| 3776e86b83 | |||
| ea60858a07 | |||
| d4488c72fb | |||
| 9146f2fb30 | |||
| 92993f967e | |||
| 2c2735af6d | |||
| 80c0e19692 | |||
| c9e2162afc | |||
| 9a52f4e3ff | |||
| c0235d4cc2 | |||
| f1704fbb57 | |||
| 38d5d3ad1b | |||
| ec96b4e3aa | |||
| aa33fd44b8 | |||
| 98865d61dd | |||
| 0036b8e2d6 | |||
| c021d26103 | |||
| 8bca5b4901 | |||
| d4db6c8912 | |||
| c93b4909f4 | |||
| 67d3c8e777 | |||
| 8d44222097 | |||
| 9cb2024334 | |||
| e71bb33b23 | |||
| 4ada7c9be9 | |||
| 56a2d8891f | |||
| 4585e90a00 | |||
| 86d2ddc168 | |||
| 9d1514308a | |||
| 7abff55981 | |||
| f9d7eba7d4 | |||
| 9ce021afa2 | |||
| 6fc9055221 | |||
| a3438d3345 | |||
| d2cbf11264 | |||
| c584156c86 | |||
| 78e04f3ad8 | |||
| 6302725678 | |||
| 431e65808d | |||
| 653914f47e | |||
| 96823e944d | |||
| ee19520e1b | |||
| 6f16b3fee7 | |||
| 89ee7e8e19 | |||
| 3c7996aa99 | |||
| 3a314c565c | |||
| 8c9b668cd7 | |||
| 7666462de2 | |||
| 54cf11a78b | |||
| 16b78f0843 | |||
| 5e97a6b192 | |||
| 595cced5b7 | |||
| d17f12dd76 | |||
| 8b5498cfbd | |||
| e4b755ced8 | |||
| 5b7eb9c332 | |||
| cd8e07c102 | |||
| a36f31c2d0 | |||
| ac0812a6dd | |||
| 69c864f984 | |||
| 3c9a7fd329 | |||
| f81dc11f61 | |||
| ce9a8f62d4 | |||
| 7e00d50078 | |||
| 4af3f5038f | |||
| 7bb1c58452 | |||
| 8e7383be05 | |||
| bc5d27ed90 | |||
| ae884d79a1 | |||
| cf89c988cf | |||
| c54313c32e | |||
| 3e001ddf1b | |||
| c41795e7f0 | |||
| 52120afdbd | |||
| 73d98da32b | |||
| 99f936ff97 | |||
| 15afaeabe3 | |||
| debf964b5f | |||
| 393730cea9 | |||
| 2194fbd535 | |||
| cf59249d3d | |||
| 2c554a3a20 | |||
| 7b9554a42c | |||
| dd527ce33c | |||
| ddcc06c6b7 | |||
| a827033f25 | |||
| 01841a4aa8 | |||
| bb52e7159c | |||
| 3988b46a60 | |||
| caa5e233df | |||
| c7609f9a2a | |||
| 750fd4efe1 | |||
| e361795184 | |||
| 64fff2adb2 | |||
| 846e398577 | |||
| c1e9ee7a66 | |||
| 8dc9e09f31 | |||
| d1930d4936 | |||
| 14539eb036 | |||
| 45f1853c44 | |||
| 6eaebd112b | |||
| f0503faeff | |||
| 64052d9dd2 | |||
| db4634a0dd | |||
| c725a2fabb | |||
| 1ddececa16 | |||
| 4e2e6cd83e | |||
| efcfe2dafc | |||
| b8ddb9e673 | |||
| a1f19e9d8a | |||
| 5464edf639 | |||
| 179c3790e6 | |||
| cfae9753a3 | |||
| 61a4a3b322 | |||
| c16bf65a80 | |||
| 16ea1912b4 | |||
| 54012cb33a | |||
| 459c5c0a55 | |||
| 4216b56443 | |||
| d7b79314d9 | |||
| a340b13f65 | |||
| 72f6b15dba | |||
| 64dbb77e63 | |||
| af10b0e4f6 | |||
| 6f15c16a42 | |||
| 86158027d7 | |||
| 50369890f7 | |||
| b8dea25aef | |||
| 64e9324aa0 | |||
| 20f8c69b07 | |||
| dd1a15c249 | |||
| 8b24498fa7 | |||
| 3673fa4908 | |||
| 960c1df5e7 | |||
| 8c3c7c18ad | |||
| b96a5af133 | |||
| d0d4008100 | |||
| 17a6fcafa1 | |||
| fb75440769 | |||
| fe39b5e4e2 | |||
| 62b142cdeb | |||
| ffce7213b4 | |||
| 4205934806 | |||
| 7aab86643a | |||
| 1bb0c55d88 | |||
| d22ac9ee00 | |||
| 80a7db2511 | |||
| e0fb102572 | |||
| 8d1a16dcd6 | |||
| 0b4bbd5db2 | |||
| 78b714e019 | |||
| 5022d81d9a | |||
| deacf28d77 | |||
| 5e8d324860 | |||
| 3554f82ea3 |
+20
-27
@@ -21,8 +21,8 @@ plugins {
|
|||||||
|
|
||||||
apply(from = "static-ips.gradle.kts")
|
apply(from = "static-ips.gradle.kts")
|
||||||
|
|
||||||
val canonicalVersionCode = 1373
|
val canonicalVersionCode = 1389
|
||||||
val canonicalVersionName = "6.43.2"
|
val canonicalVersionName = "6.47.0"
|
||||||
|
|
||||||
val postFixSize = 100
|
val postFixSize = 100
|
||||||
val abiPostFix: Map<String, Int> = mapOf(
|
val abiPostFix: Map<String, Int> = mapOf(
|
||||||
@@ -98,7 +98,6 @@ android {
|
|||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = signalKotlinJvmTarget
|
jvmTarget = signalKotlinJvmTarget
|
||||||
freeCompilerArgs = listOf("-Xallow-result-return-type")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keystores["debug"]?.let { properties ->
|
keystores["debug"]?.let { properties ->
|
||||||
@@ -164,8 +163,8 @@ android {
|
|||||||
versionCode = canonicalVersionCode * postFixSize
|
versionCode = canonicalVersionCode * postFixSize
|
||||||
versionName = canonicalVersionName
|
versionName = canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion(signalMinSdkVersion)
|
minSdk = signalMinSdkVersion
|
||||||
targetSdkVersion(signalTargetSdkVersion)
|
targetSdk = signalTargetSdkVersion
|
||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
@@ -415,9 +414,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
val variant = this
|
outputs
|
||||||
|
|
||||||
variant.outputs
|
|
||||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
.forEach { output ->
|
.forEach { output ->
|
||||||
if (output.baseName.contains("nightly")) {
|
if (output.baseName.contains("nightly")) {
|
||||||
@@ -430,10 +427,10 @@ android {
|
|||||||
output.versionNameOverride = tag
|
output.versionNameOverride = tag
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
||||||
} else {
|
} else {
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
|
||||||
|
|
||||||
val abiName: String = output.getFilter("ABI") ?: "universal"
|
val abiName: String = output.getFilter("ABI") ?: "universal"
|
||||||
val postFix: Int = abiPostFix[abiName]!!
|
val postFix: Int = abiPostFix[abiName]!!
|
||||||
@@ -447,25 +444,20 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.variantFilter {
|
androidComponents {
|
||||||
val distribution: String = flavors[0].name
|
beforeVariants { variant ->
|
||||||
val environment: String = flavors[1].name
|
variant.enable = variant.name in selectableVariants
|
||||||
val buildType: String = buildType.name
|
|
||||||
val fullName: String = distribution + environment.capitalize() + buildType.capitalize()
|
|
||||||
|
|
||||||
if (!selectableVariants.contains(fullName)) {
|
|
||||||
ignore = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.buildTypes.forEach {
|
val releaseDir = "$projectDir/src/release/java"
|
||||||
val path: String = if (it.name == "release") {
|
val debugDir = "$projectDir/src/debug/java"
|
||||||
"$projectDir/src/release/java"
|
|
||||||
} else {
|
|
||||||
"$projectDir/src/debug/java"
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets.findByName(it.name)!!.java.srcDir(path)
|
android.buildTypes.configureEach {
|
||||||
|
val path = if (name == "release") releaseDir else debugDir
|
||||||
|
sourceSets.named(name) {
|
||||||
|
java.srcDir(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,10 +476,8 @@ dependencies {
|
|||||||
implementation(project(":donations"))
|
implementation(project(":donations"))
|
||||||
implementation(project(":contacts"))
|
implementation(project(":contacts"))
|
||||||
implementation(project(":qr"))
|
implementation(project(":qr"))
|
||||||
implementation(project(":sms-exporter"))
|
|
||||||
implementation(project(":sticky-header-grid"))
|
implementation(project(":sticky-header-grid"))
|
||||||
implementation(project(":photoview"))
|
implementation(project(":photoview"))
|
||||||
implementation(project(":glide-webp"))
|
|
||||||
implementation(project(":core-ui"))
|
implementation(project(":core-ui"))
|
||||||
|
|
||||||
implementation(libs.androidx.fragment.ktx)
|
implementation(libs.androidx.fragment.ktx)
|
||||||
@@ -507,16 +497,19 @@ dependencies {
|
|||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
implementation(libs.androidx.compose.rxjava3)
|
implementation(libs.androidx.compose.rxjava3)
|
||||||
implementation(libs.androidx.compose.runtime.livedata)
|
implementation(libs.androidx.compose.runtime.livedata)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.androidx.multidex)
|
implementation(libs.androidx.multidex)
|
||||||
implementation(libs.androidx.navigation.fragment.ktx)
|
implementation(libs.androidx.navigation.fragment.ktx)
|
||||||
implementation(libs.androidx.navigation.ui.ktx)
|
implementation(libs.androidx.navigation.ui.ktx)
|
||||||
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||||
implementation(libs.androidx.lifecycle.livedata.ktx)
|
implementation(libs.androidx.lifecycle.livedata.ktx)
|
||||||
implementation(libs.androidx.lifecycle.process)
|
implementation(libs.androidx.lifecycle.process)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
||||||
implementation(libs.androidx.lifecycle.common.java8)
|
implementation(libs.androidx.lifecycle.common.java8)
|
||||||
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
|
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.camera.core)
|
implementation(libs.androidx.camera.core)
|
||||||
implementation(libs.androidx.camera.camera2)
|
implementation(libs.androidx.camera.camera2)
|
||||||
implementation(libs.androidx.camera.lifecycle)
|
implementation(libs.androidx.camera.lifecycle)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class BackupTest {
|
|||||||
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
|
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
|
||||||
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
|
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
|
||||||
|
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED
|
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
|
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
|
||||||
|
|
||||||
SignalStore.settings().isLinkPreviewsEnabled = false
|
SignalStore.settings().isLinkPreviewsEnabled = false
|
||||||
|
|||||||
+9
-2
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.groups.GroupId
|
|||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
@@ -209,8 +209,9 @@ class V2ConversationItemShapeTest {
|
|||||||
override val selectedItems: Set<MultiselectPart> = emptySet()
|
override val selectedItems: Set<MultiselectPart> = emptySet()
|
||||||
override val isMessageRequestAccepted: Boolean = true
|
override val isMessageRequestAccepted: Boolean = true
|
||||||
override val searchQuery: String? = null
|
override val searchQuery: String? = null
|
||||||
override val glideRequests: GlideRequests = mockk()
|
override val requestManager: RequestManager = mockk()
|
||||||
override val isParentInScroll: Boolean = false
|
override val isParentInScroll: Boolean = false
|
||||||
|
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
|
||||||
|
|
||||||
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
||||||
|
|
||||||
@@ -321,5 +322,11 @@ class V2ConversationItemShapeTest {
|
|||||||
override fun onItemClick(item: MultiselectPart?) = Unit
|
override fun onItemClick(item: MultiselectPart?) = Unit
|
||||||
|
|
||||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
|
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
|
||||||
|
|
||||||
|
override fun onShowSafetyTips(forGroup: Boolean) = Unit
|
||||||
|
|
||||||
|
override fun onReportSpamLearnMoreClicked() = Unit
|
||||||
|
|
||||||
|
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -61,8 +61,8 @@ class AttachmentTableTest {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
||||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
||||||
|
|
||||||
assertNotEquals(attachment1Info, attachment2Info)
|
assertNotEquals(attachment1Info, attachment2Info)
|
||||||
}
|
}
|
||||||
@@ -89,8 +89,8 @@ class AttachmentTableTest {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
||||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
||||||
|
|
||||||
assertNotEquals(attachment1Info, attachment2Info)
|
assertNotEquals(attachment1Info, attachment2Info)
|
||||||
}
|
}
|
||||||
@@ -124,9 +124,9 @@ class AttachmentTableTest {
|
|||||||
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
|
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA)!!
|
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
|
|
||||||
assertNotEquals(standardInfo, highInfo)
|
assertNotEquals(standardInfo, highInfo)
|
||||||
standardInfo.file assertIs previousInfo.file
|
standardInfo.file assertIs previousInfo.file
|
||||||
@@ -158,9 +158,9 @@ class AttachmentTableTest {
|
|||||||
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||||
|
|
||||||
highInfo.file assertIsNot standardInfo.file
|
highInfo.file assertIsNot standardInfo.file
|
||||||
secondHighInfo.file assertIs highInfo.file
|
secondHighInfo.file assertIs highInfo.file
|
||||||
|
|||||||
@@ -214,6 +214,175 @@ class CallTableTest {
|
|||||||
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
|
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAnOutgoingRingCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
|
||||||
|
val callId = 1L
|
||||||
|
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||||
|
callId = callId,
|
||||||
|
recipientId = groupRecipientId,
|
||||||
|
direction = CallTable.Direction.OUTGOING,
|
||||||
|
timestamp = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenARingingCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
|
||||||
|
val callId = 1L
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||||
|
ringId = callId,
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
ringerRecipient = harness.others[1],
|
||||||
|
dateReceived = System.currentTimeMillis(),
|
||||||
|
ringState = CallManager.RingUpdate.REQUESTED
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAMissedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
|
||||||
|
val callId = 1L
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||||
|
ringId = callId,
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
ringerRecipient = harness.others[1],
|
||||||
|
dateReceived = System.currentTimeMillis(),
|
||||||
|
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenADeclinedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
|
||||||
|
val callId = 1L
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||||
|
ringId = callId,
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
ringerRecipient = harness.others[1],
|
||||||
|
dateReceived = System.currentTimeMillis(),
|
||||||
|
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAnAcceptedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
|
||||||
|
val callId = 1L
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||||
|
ringId = callId,
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
ringerRecipient = harness.others[1],
|
||||||
|
dateReceived = System.currentTimeMillis(),
|
||||||
|
ringState = CallManager.RingUpdate.REQUESTED
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
SignalDatabase.calls.getCallById(callId, groupRecipientId)!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAGenericGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
|
||||||
|
val era = "aaa"
|
||||||
|
val callId = CallId.fromEra(era).longValue()
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
sender = harness.others[1],
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
peekGroupCallEraId = "aaa",
|
||||||
|
peekJoinedUuids = emptyList(),
|
||||||
|
isCallFull = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAJoinedGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
|
||||||
|
val era = "aaa"
|
||||||
|
val callId = CallId.fromEra(era).longValue()
|
||||||
|
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||||
|
groupRecipientId = groupRecipientId,
|
||||||
|
sender = harness.others[1],
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
peekGroupCallEraId = "aaa",
|
||||||
|
peekJoinedUuids = emptyList(),
|
||||||
|
isCallFull = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertNotNull(call)
|
||||||
|
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||||
|
call!!
|
||||||
|
)
|
||||||
|
|
||||||
|
SignalDatabase.calls.acceptOutgoingGroupCall(SignalDatabase.calls.getCallById(callId, groupRecipientId)!!)
|
||||||
|
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||||
|
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
|
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
|
||||||
val era = "aaa"
|
val era = "aaa"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.junit.Assert.assertTrue
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.signal.core.util.readToList
|
import org.signal.core.util.readToList
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.withinTransaction
|
import org.signal.core.util.withinTransaction
|
||||||
@@ -33,8 +33,8 @@ class GroupTableTest {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
groupTable = SignalDatabase.groups
|
groupTable = SignalDatabase.groups
|
||||||
|
|
||||||
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
|
groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
|
||||||
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
|
groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.signal.core.util.forEach
|
|||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.requireNonNullString
|
import org.signal.core.util.requireNonNullString
|
||||||
import org.signal.core.util.select
|
import org.signal.core.util.select
|
||||||
import org.signal.core.util.update
|
import org.signal.core.util.updateAll
|
||||||
import org.thoughtcrime.securesms.crash.CrashConfig
|
import org.thoughtcrime.securesms.crash.CrashConfig
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.testing.assertIs
|
import org.thoughtcrime.securesms.testing.assertIs
|
||||||
@@ -220,7 +220,7 @@ class LogDatabaseTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
db.writableDatabase
|
db.writableDatabase
|
||||||
.update(LogDatabase.CrashTable.TABLE_NAME)
|
.updateAll(LogDatabase.CrashTable.TABLE_NAME)
|
||||||
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class RecipientTableTest {
|
|||||||
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
|
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
|
||||||
|
|
||||||
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!)
|
SignalDatabase.recipients.splitForStorageSyncIfNecessary(mainRecord.aci!!)
|
||||||
|
|
||||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||||
|
|
||||||
|
|||||||
+42
@@ -18,6 +18,7 @@ import org.signal.core.util.Base64
|
|||||||
import org.signal.core.util.SqlUtil
|
import org.signal.core.util.SqlUtil
|
||||||
import org.signal.core.util.exists
|
import org.signal.core.util.exists
|
||||||
import org.signal.core.util.orNull
|
import org.signal.core.util.orNull
|
||||||
|
import org.signal.core.util.readToSingleBoolean
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.requireNonNullString
|
import org.signal.core.util.requireNonNullString
|
||||||
import org.signal.core.util.select
|
import org.signal.core.util.select
|
||||||
@@ -109,6 +110,18 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
val record = SignalDatabase.recipients.getRecord(id)
|
val record = SignalDatabase.recipients.getRecord(id)
|
||||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("e164+pni+aci insert, pni verified") {
|
||||||
|
val id = process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||||
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
expectPniVerified()
|
||||||
|
|
||||||
|
val record = SignalDatabase.recipients.getRecord(id)
|
||||||
|
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||||
|
|
||||||
|
process(E164_A, PNI_A, ACI_A, pniVerified = false)
|
||||||
|
expectPniVerified()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -164,6 +177,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
expectNoSessionSwitchoverEvent()
|
expectNoSessionSwitchoverEvent()
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("no match, all fields") {
|
test("no match, all fields") {
|
||||||
@@ -225,6 +239,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
given(E164_A, PNI_A, null, pniSession = true)
|
given(E164_A, PNI_A, null, pniSession = true)
|
||||||
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("e164 and aci matches, all provided, new pni") {
|
test("e164 and aci matches, all provided, new pni") {
|
||||||
@@ -694,6 +710,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
|
|
||||||
expectDeleted()
|
expectDeleted()
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("merge, e164+pni & aci, pni session, pni verified") {
|
test("merge, e164+pni & aci, pni session, pni verified") {
|
||||||
@@ -706,6 +724,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
expectThreadMergeEvent(E164_A)
|
expectThreadMergeEvent(E164_A)
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("merge, e164+pni & e164+pni+aci, change number") {
|
test("merge, e164+pni & e164+pni+aci, change number") {
|
||||||
@@ -1037,6 +1056,10 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
if (!test.sessionSwitchoverExpected) {
|
if (!test.sessionSwitchoverExpected) {
|
||||||
test.expectNoSessionSwitchoverEvent()
|
test.expectNoSessionSwitchoverEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!test.pniVerifiedExpected) {
|
||||||
|
test.expectPniNotVerified()
|
||||||
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e.javaClass != exception) {
|
if (e.javaClass != exception) {
|
||||||
val error = java.lang.AssertionError("[$name] ${e.message}")
|
val error = java.lang.AssertionError("[$name] ${e.message}")
|
||||||
@@ -1056,6 +1079,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
var changeNumberExpected = false
|
var changeNumberExpected = false
|
||||||
var threadMergeExpected = false
|
var threadMergeExpected = false
|
||||||
var sessionSwitchoverExpected = false
|
var sessionSwitchoverExpected = false
|
||||||
|
var pniVerifiedExpected = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Need to delete these first to prevent foreign key crash
|
// Need to delete these first to prevent foreign key crash
|
||||||
@@ -1207,6 +1231,24 @@ class RecipientTableTest_getAndPossiblyMerge {
|
|||||||
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
|
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun expectPniVerified() {
|
||||||
|
assertTrue("Expected PNI to be verified!", isPniVerified(outputRecipientId))
|
||||||
|
pniVerifiedExpected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expectPniNotVerified() {
|
||||||
|
assertFalse("Expected PNI to be not be verified!", isPniVerified(outputRecipientId))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPniVerified(recipientId: RecipientId): Boolean {
|
||||||
|
return SignalDatabase.rawDatabase
|
||||||
|
.select(RecipientTable.PNI_SIGNATURE_VERIFIED)
|
||||||
|
.from(RecipientTable.TABLE_NAME)
|
||||||
|
.where("${RecipientTable.ID} = ?", recipientId)
|
||||||
|
.run()
|
||||||
|
.readToSingleBoolean(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
||||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||||
RecipientTable.TABLE_NAME,
|
RecipientTable.TABLE_NAME,
|
||||||
|
|||||||
+2
-1
@@ -290,7 +290,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
|||||||
from = sender,
|
from = sender,
|
||||||
timestamp = wallClock,
|
timestamp = wallClock,
|
||||||
groupId = groupId,
|
groupId = groupId,
|
||||||
groupContext = groupContext
|
groupContext = groupContext,
|
||||||
|
serverGuid = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -39,7 +39,6 @@ class EditMessageSyncProcessorTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
|
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
|
||||||
AttachmentTable.UNIQUE_ID,
|
|
||||||
AttachmentTable.TRANSFER_FILE
|
AttachmentTable.TRANSFER_FILE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
|
|||||||
import org.thoughtcrime.securesms.testing.assertIsNotNull
|
import org.thoughtcrime.securesms.testing.assertIsNotNull
|
||||||
import org.thoughtcrime.securesms.testing.assertIsNull
|
import org.thoughtcrime.securesms.testing.assertIsNull
|
||||||
import org.thoughtcrime.securesms.testing.success
|
import org.thoughtcrime.securesms.testing.success
|
||||||
|
import org.whispersystems.signalservice.api.util.Usernames
|
||||||
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
|
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ class UsernameEditFragmentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testUsernameCreationInRegistration() {
|
fun testUsernameCreationInRegistration() {
|
||||||
val scenario = createScenario(true)
|
val scenario = createScenario(UsernameEditMode.REGISTRATION)
|
||||||
|
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ class UsernameEditFragmentTest {
|
|||||||
@Ignore("Flakey espresso test.")
|
@Ignore("Flakey espresso test.")
|
||||||
@Test
|
@Test
|
||||||
fun testUsernameCreationOutsideOfRegistration() {
|
fun testUsernameCreationOutsideOfRegistration() {
|
||||||
val scenario = createScenario()
|
val scenario = createScenario(UsernameEditMode.NORMAL)
|
||||||
|
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ class UsernameEditFragmentTest {
|
|||||||
fun testNicknameUpdateHappyPath() {
|
fun testNicknameUpdateHappyPath() {
|
||||||
val nickname = "Spiderman"
|
val nickname = "Spiderman"
|
||||||
val discriminator = "4578"
|
val discriminator = "4578"
|
||||||
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
|
val username = "$nickname${Usernames.DELIMITER}$discriminator"
|
||||||
|
|
||||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
Put("/v1/accounts/username/reserved") {
|
Put("/v1/accounts/username/reserved") {
|
||||||
@@ -107,7 +108,7 @@ class UsernameEditFragmentTest {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val scenario = createScenario(isInRegistration = true)
|
val scenario = createScenario(UsernameEditMode.REGISTRATION)
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
onView(withId(R.id.username_text)).perform(typeText(nickname))
|
onView(withId(R.id.username_text)).perform(typeText(nickname))
|
||||||
@@ -131,8 +132,8 @@ class UsernameEditFragmentTest {
|
|||||||
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
|
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
|
private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
|
||||||
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
|
val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
|
||||||
return launchFragmentInContainer(
|
return launchFragmentInContainer(
|
||||||
fragmentArgs = fragmentArgs,
|
fragmentArgs = fragmentArgs,
|
||||||
themeResId = R.style.Signal_DayNight_NoActionBar
|
themeResId = R.style.Signal_DayNight_NoActionBar
|
||||||
|
|||||||
+30
-1
@@ -33,7 +33,7 @@ class ContactRecordProcessorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun process_splitContact_normalSplit() {
|
fun process_splitContact_normalSplit_twoRecords() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
setStorageId(originalId, STORAGE_ID_A)
|
setStorageId(originalId, STORAGE_ID_A)
|
||||||
@@ -69,6 +69,35 @@ class ContactRecordProcessorTest {
|
|||||||
assertNotEquals(byAci, byE164)
|
assertNotEquals(byAci, byE164)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun process_splitContact_normalSplit_oneRecord() {
|
||||||
|
// GIVEN
|
||||||
|
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
|
setStorageId(originalId, STORAGE_ID_A)
|
||||||
|
|
||||||
|
val remote = buildRecord(
|
||||||
|
STORAGE_ID_B,
|
||||||
|
ContactRecord(
|
||||||
|
aci = ACI_A.toString(),
|
||||||
|
unregisteredAtTimestamp = 100
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val subject = ContactRecordProcessor()
|
||||||
|
subject.process(listOf(remote), StorageSyncHelper.KEY_GENERATOR)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||||
|
|
||||||
|
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||||
|
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||||
|
|
||||||
|
assertEquals(originalId, byAci)
|
||||||
|
assertEquals(byE164, byPni)
|
||||||
|
assertNotEquals(byAci, byE164)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
|
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.testing
|
|||||||
|
|
||||||
import org.junit.rules.TestWatcher
|
import org.junit.rules.TestWatcher
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
|
import org.signal.core.util.deleteAll
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadTable
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
@@ -34,7 +36,8 @@ class SignalDatabaseRule(
|
|||||||
|
|
||||||
private fun deleteAllThreads() {
|
private fun deleteAllThreads() {
|
||||||
if (deleteAllThreadsOnEachRun) {
|
if (deleteAllThreadsOnEachRun) {
|
||||||
SignalDatabase.threads.clearForTests()
|
SignalDatabase.threads.deleteAllConversations()
|
||||||
|
SignalDatabase.rawDatabase.deleteAll(ThreadTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -117,7 +117,9 @@ class ConversationElementGenerator {
|
|||||||
-1,
|
-1,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
0
|
0,
|
||||||
|
false,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
||||||
|
|||||||
+17
-3
@@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.navigation.navGraphViewModels
|
import androidx.navigation.navGraphViewModels
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||||
@@ -33,6 +34,7 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer
|
|||||||
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
|
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
|
||||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
|
||||||
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
|
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
@@ -41,7 +43,6 @@ import org.thoughtcrime.securesms.groups.GroupId
|
|||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
@@ -61,11 +62,12 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val adapter = ConversationAdapterV2(
|
val adapter = ConversationAdapterV2(
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
glideRequests = GlideApp.with(this),
|
requestManager = Glide.with(this),
|
||||||
clickListener = ClickListener(),
|
clickListener = ClickListener(),
|
||||||
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
||||||
colorizer = Colorizer(),
|
colorizer = Colorizer(),
|
||||||
startExpirationTimeout = {}
|
startExpirationTimeout = {},
|
||||||
|
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (springboardViewModel.hasWallpaper.value) {
|
if (springboardViewModel.hasWallpaper.value) {
|
||||||
@@ -296,5 +298,17 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
|||||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
||||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onShowSafetyTips(forGroup: Boolean) {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReportSpamLearnMoreClicked() {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageRequestAcceptOptionsClicked() {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,6 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.SEND_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||||
|
|
||||||
@@ -160,12 +155,6 @@
|
|||||||
android:value=".MainActivity" />
|
android:value=".MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".PromptMmsActivity"
|
|
||||||
android:label="Configure MMS Settings"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<activity android:name=".DeviceProvisioningActivity"
|
<activity android:name=".DeviceProvisioningActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
@@ -693,7 +682,12 @@
|
|||||||
|
|
||||||
<activity android:name=".NewConversationActivity"
|
<activity android:name=".NewConversationActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateAlwaysVisible"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity android:name=".recipients.ui.findby.FindByActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
@@ -768,6 +762,13 @@
|
|||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".backup.v2.ui.MessageBackupsFlowActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".stories.settings.StorySettingsActivity"
|
android:name=".stories.settings.StorySettingsActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
@@ -1072,13 +1073,6 @@
|
|||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
|
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
@@ -1100,24 +1094,11 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".exporter.flow.SmsExportActivity"
|
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
|
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<service
|
|
||||||
android:enabled="true"
|
|
||||||
android:name=".exporter.SignalSmsExportService"
|
|
||||||
android:foregroundServiceType="dataSync"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:name=".service.webrtc.WebRtcCallService"
|
android:name=".service.webrtc.WebRtcCallService"
|
||||||
@@ -1396,6 +1377,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<service android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallForegroundService" android:exported="false" />
|
||||||
|
<receiver android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallServiceReceiver" android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.DENY"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.HANGUP"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
+2816
-2429
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.multidex.MultiDexApplication;
|
import androidx.multidex.MultiDexApplication;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.gms.security.ProviderInstaller;
|
import com.google.android.gms.security.ProviderInstaller;
|
||||||
|
|
||||||
import org.conscrypt.ConscryptSignal;
|
import org.conscrypt.ConscryptSignal;
|
||||||
@@ -57,6 +58,7 @@ import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.ExternalLaunchDonationJob;
|
import org.thoughtcrime.securesms.jobs.ExternalLaunchDonationJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.GroupRingCleanupJob;
|
||||||
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
|
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
|
||||||
@@ -73,7 +75,6 @@ import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
|||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
|
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
||||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
@@ -177,7 +178,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||||
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
||||||
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
|
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
|
||||||
.addNonBlocking(() -> GlideApp.get(this))
|
.addNonBlocking(() -> Glide.get(this))
|
||||||
.addNonBlocking(this::cleanAvatarStorage)
|
.addNonBlocking(this::cleanAvatarStorage)
|
||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||||
@@ -214,6 +215,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
|
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
|
||||||
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
||||||
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
|
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
|
||||||
|
.addPostRender(GroupRingCleanupJob::enqueue)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.DataSource;
|
import com.bumptech.glide.load.DataSource;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.engine.GlideException;
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
@@ -32,7 +33,6 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
|||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||||
@@ -96,7 +96,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
|||||||
|
|
||||||
Resources resources = this.getResources();
|
Resources resources = this.getResources();
|
||||||
|
|
||||||
GlideApp.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(contactPhoto)
|
.load(contactPhoto)
|
||||||
.fallback(fallbackPhoto.asCallCard(this))
|
.fallback(fallbackPhoto.asCallCard(this))
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.signal.ringrtc.CallLinkRootKey;
|
import org.signal.ringrtc.CallLinkRootKey;
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
@@ -26,7 +28,6 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
|||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
@@ -41,7 +42,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
|||||||
@NonNull ConversationMessage messageRecord,
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull RequestManager requestManager,
|
||||||
@NonNull Locale locale,
|
@NonNull Locale locale,
|
||||||
@NonNull Set<MultiselectPart> batchSelected,
|
@NonNull Set<MultiselectPart> batchSelected,
|
||||||
@NonNull Recipient recipients,
|
@NonNull Recipient recipients,
|
||||||
@@ -122,5 +123,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
|||||||
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
|
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
|
||||||
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
|
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
|
||||||
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
|
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
|
||||||
|
void onShowSafetyTips(boolean forGroup);
|
||||||
|
void onReportSpamLearnMoreClicked();
|
||||||
|
void onMessageRequestAcceptOptionsClicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package org.thoughtcrime.securesms;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -14,7 +15,7 @@ public interface BindableConversationListItem extends Unbindable {
|
|||||||
|
|
||||||
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
@NonNull ThreadRecord thread,
|
@NonNull ThreadRecord thread,
|
||||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
@NonNull RequestManager requestManager, @NonNull Locale locale,
|
||||||
@NonNull Set<Long> typingThreads,
|
@NonNull Set<Long> typingThreads,
|
||||||
@NonNull ConversationSet selectedConversations);
|
@NonNull ConversationSet selectedConversations);
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,36 @@ import androidx.lifecycle.Lifecycle;
|
|||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.signal.core.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
||||||
*/
|
*/
|
||||||
public final class BlockUnblockDialog {
|
public final class BlockUnblockDialog {
|
||||||
|
|
||||||
private BlockUnblockDialog() { }
|
private BlockUnblockDialog() {}
|
||||||
|
|
||||||
|
public static void showReportSpamFor(@NonNull Context context,
|
||||||
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onReportSpam,
|
||||||
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
|
{
|
||||||
|
SimpleTask.run(lifecycle,
|
||||||
|
() -> buildReportSpamFor(context, recipient, onReportSpam, onBlockAndReportSpam),
|
||||||
|
AlertDialog.Builder::show);
|
||||||
|
}
|
||||||
|
|
||||||
public static void showBlockFor(@NonNull Context context,
|
public static void showBlockFor(@NonNull Context context,
|
||||||
@NonNull Lifecycle lifecycle,
|
@NonNull Lifecycle lifecycle,
|
||||||
@@ -137,4 +157,37 @@ public final class BlockUnblockDialog {
|
|||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static AlertDialog.Builder buildReportSpamFor(@NonNull Context context,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onReportSpam,
|
||||||
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
|
{
|
||||||
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.BlockUnblockDialog_report_spam_title)
|
||||||
|
.setPositiveButton(R.string.BlockUnblockDialog_report_spam, (d, w) -> onReportSpam.run());
|
||||||
|
|
||||||
|
if (onBlockAndReportSpam != null) {
|
||||||
|
builder.setNeutralButton(android.R.string.cancel, null)
|
||||||
|
.setNegativeButton(R.string.BlockUnblockDialog_report_spam_and_block, (d, w) -> onBlockAndReportSpam.run());
|
||||||
|
} else {
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
Recipient adder = SignalDatabase.groups().getGroupInviter(recipient.requireGroupId());
|
||||||
|
if (adder != null) {
|
||||||
|
builder.setMessage(context.getString(R.string.BlockUnblockDialog_report_spam_group_named_adder, adder.getDisplayName(context)));
|
||||||
|
} else {
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_report_spam_group_unknown_adder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_report_spam_description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,17 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.signal.core.util.DimensionUnit;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.DisplayMetricsUtil;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
@@ -99,6 +102,10 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
|
|
||||||
private void initializeContactFilterView() {
|
private void initializeContactFilterView() {
|
||||||
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||||
|
|
||||||
|
if (getResources().getDisplayMetrics().heightPixels >= DimensionUnit.DP.toPixels(600) || !FeatureFlags.usernames()) {
|
||||||
|
this.contactFilterView.focusAndShowKeyboard();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeToolbar() {
|
private void initializeToolbar() {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class ContactSelectionListAdapter(
|
|||||||
registerFactory(RefreshContactsModel::class.java, LayoutFactory({ RefreshContactsViewHolder(it, onClickCallbacks::onRefreshContactsClicked) }, R.layout.contact_selection_refresh_action_item))
|
registerFactory(RefreshContactsModel::class.java, LayoutFactory({ RefreshContactsViewHolder(it, onClickCallbacks::onRefreshContactsClicked) }, R.layout.contact_selection_refresh_action_item))
|
||||||
registerFactory(MoreHeaderModel::class.java, LayoutFactory({ MoreHeaderViewHolder(it) }, R.layout.contact_search_section_header))
|
registerFactory(MoreHeaderModel::class.java, LayoutFactory({ MoreHeaderViewHolder(it) }, R.layout.contact_search_section_header))
|
||||||
registerFactory(EmptyModel::class.java, LayoutFactory({ EmptyViewHolder(it) }, R.layout.contact_selection_empty_state))
|
registerFactory(EmptyModel::class.java, LayoutFactory({ EmptyViewHolder(it) }, R.layout.contact_selection_empty_state))
|
||||||
|
registerFactory(FindByUsernameModel::class.java, LayoutFactory({ FindByUsernameViewHolder(it, onClickCallbacks::onFindByUsernameClicked) }, R.layout.contact_selection_find_by_username_item))
|
||||||
|
registerFactory(FindByPhoneNumberModel::class.java, LayoutFactory({ FindByPhoneNumberViewHolder(it, onClickCallbacks::onFindByPhoneNumberClicked) }, R.layout.contact_selection_find_by_phone_number_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewGroupModel : MappingModel<NewGroupModel> {
|
class NewGroupModel : MappingModel<NewGroupModel> {
|
||||||
@@ -44,6 +46,16 @@ class ContactSelectionListAdapter(
|
|||||||
override fun areContentsTheSame(newItem: RefreshContactsModel): Boolean = true
|
override fun areContentsTheSame(newItem: RefreshContactsModel): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FindByUsernameModel : MappingModel<FindByUsernameModel> {
|
||||||
|
override fun areItemsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
|
override fun areContentsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindByPhoneNumberModel : MappingModel<FindByPhoneNumberModel> {
|
||||||
|
override fun areItemsTheSame(newItem: FindByPhoneNumberModel): Boolean = true
|
||||||
|
override fun areContentsTheSame(newItem: FindByPhoneNumberModel): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
class MoreHeaderModel : MappingModel<MoreHeaderModel> {
|
class MoreHeaderModel : MappingModel<MoreHeaderModel> {
|
||||||
override fun areItemsTheSame(newItem: MoreHeaderModel): Boolean = true
|
override fun areItemsTheSame(newItem: MoreHeaderModel): Boolean = true
|
||||||
|
|
||||||
@@ -92,13 +104,33 @@ class ContactSelectionListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FindByPhoneNumberViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<FindByPhoneNumberModel>(itemView) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener { onClickListener() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(model: FindByPhoneNumberModel) = Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FindByUsernameViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<FindByUsernameModel>(itemView) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener { onClickListener() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(model: FindByUsernameModel) = Unit
|
||||||
|
}
|
||||||
|
|
||||||
class ArbitraryRepository : org.thoughtcrime.securesms.contacts.paged.ArbitraryRepository {
|
class ArbitraryRepository : org.thoughtcrime.securesms.contacts.paged.ArbitraryRepository {
|
||||||
|
|
||||||
enum class ArbitraryRow(val code: String) {
|
enum class ArbitraryRow(val code: String) {
|
||||||
NEW_GROUP("new-group"),
|
NEW_GROUP("new-group"),
|
||||||
INVITE_TO_SIGNAL("invite-to-signal"),
|
INVITE_TO_SIGNAL("invite-to-signal"),
|
||||||
MORE_HEADING("more-heading"),
|
MORE_HEADING("more-heading"),
|
||||||
REFRESH_CONTACTS("refresh-contacts");
|
REFRESH_CONTACTS("refresh-contacts"),
|
||||||
|
FIND_BY_USERNAME("find-by-username"),
|
||||||
|
FIND_BY_PHONE_NUMBER("find-by-phone-number");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromCode(code: String) = values().first { it.code == code }
|
fun fromCode(code: String) = values().first { it.code == code }
|
||||||
@@ -120,6 +152,8 @@ class ContactSelectionListAdapter(
|
|||||||
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
||||||
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
||||||
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
||||||
|
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
|
||||||
|
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,5 +162,7 @@ class ContactSelectionListAdapter(
|
|||||||
fun onNewGroupClicked()
|
fun onNewGroupClicked()
|
||||||
fun onInviteToSignalClicked()
|
fun onInviteToSignalClicked()
|
||||||
fun onRefreshContactsClicked()
|
fun onRefreshContactsClicked()
|
||||||
|
fun onFindByPhoneNumberClicked()
|
||||||
|
fun onFindByUsernameClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAci
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
@@ -141,6 +142,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
private ContactSearchMediator contactSearchMediator;
|
private ContactSearchMediator contactSearchMediator;
|
||||||
|
|
||||||
@Nullable private NewConversationCallback newConversationCallback;
|
@Nullable private NewConversationCallback newConversationCallback;
|
||||||
|
@Nullable private FindByCallback findByCallback;
|
||||||
@Nullable private NewCallCallback newCallCallback;
|
@Nullable private NewCallCallback newCallCallback;
|
||||||
@Nullable private ScrollCallback scrollCallback;
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
||||||
@@ -161,6 +163,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
newConversationCallback = (NewConversationCallback) context;
|
newConversationCallback = (NewConversationCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context instanceof FindByCallback) {
|
||||||
|
findByCallback = (FindByCallback) context;
|
||||||
|
}
|
||||||
|
|
||||||
if (context instanceof NewCallCallback) {
|
if (context instanceof NewCallCallback) {
|
||||||
newCallCallback = (NewCallCallback) context;
|
newCallCallback = (NewCallCallback) context;
|
||||||
}
|
}
|
||||||
@@ -379,6 +385,16 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
newConversationCallback.onNewGroup(false);
|
newConversationCallback.onNewGroup(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByPhoneNumberClicked() {
|
||||||
|
findByCallback.onFindByPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByUsernameClicked() {
|
||||||
|
findByCallback.onFindByUsername();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInviteToSignalClicked() {
|
public void onInviteToSignalClicked() {
|
||||||
if (newConversationCallback != null) {
|
if (newConversationCallback != null) {
|
||||||
@@ -660,6 +676,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addRecipientToSelectionIfAble(@NonNull RecipientId recipientId) {
|
||||||
|
listClickListener.onItemClick(new ContactSearchKey.RecipientSearchKey(recipientId, false));
|
||||||
|
}
|
||||||
|
|
||||||
private class ListClickListener {
|
private class ListClickListener {
|
||||||
public void onItemClick(ContactSearchKey contact) {
|
public void onItemClick(ContactSearchKey contact) {
|
||||||
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
||||||
@@ -874,6 +894,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (findByCallback != null && FeatureFlags.usernames()) {
|
||||||
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_BY_USERNAME.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_BY_PHONE_NUMBER.getCode());
|
||||||
|
|
||||||
if (transportType != null) {
|
if (transportType != null) {
|
||||||
if (!hasQuery && includeRecents) {
|
if (!hasQuery && includeRecents) {
|
||||||
builder.addSection(new ContactSearchConfiguration.Section.Recents(
|
builder.addSection(new ContactSearchConfiguration.Section.Recents(
|
||||||
@@ -891,7 +917,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
|
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
|
||||||
includeSelf,
|
includeSelf,
|
||||||
transportType,
|
transportType,
|
||||||
newCallCallback == null,
|
newCallCallback == null && findByCallback == null,
|
||||||
null,
|
null,
|
||||||
!hideLetterHeaders()
|
!hideLetterHeaders()
|
||||||
));
|
));
|
||||||
@@ -1011,6 +1037,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
void onNewGroup(boolean forceV1);
|
void onNewGroup(boolean forceV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface FindByCallback {
|
||||||
|
void onFindByUsername();
|
||||||
|
|
||||||
|
void onFindByPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
public interface NewCallCallback {
|
public interface NewCallCallback {
|
||||||
void onInvite();
|
void onInvite();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
|||||||
.setOnDismissListener(dialog13 -> finish())
|
.setOnDismissListener(dialog13 -> finish())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
dialog.setIcon(getResources().getDrawable(R.drawable.icon_dialog));
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.signal.donations.StripeApi;
|
import org.signal.donations.StripeApi;
|
||||||
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment;
|
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment;
|
||||||
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment;
|
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment;
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity;
|
||||||
|
import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
@@ -70,14 +72,15 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class NewConversationActivity extends ContactSelectionActivity
|
public class NewConversationActivity extends ContactSelectionActivity
|
||||||
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener
|
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener, ContactSelectionListFragment.FindByCallback
|
||||||
{
|
{
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||||
|
|
||||||
private ContactsManagementViewModel viewModel;
|
private ContactsManagementViewModel viewModel;
|
||||||
private ActivityResultLauncher<Intent> contactLauncher;
|
private ActivityResultLauncher<Intent> contactLauncher;
|
||||||
|
private ActivityResultLauncher<FindByMode> findByLauncher;
|
||||||
|
|
||||||
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
||||||
|
|
||||||
@@ -99,6 +102,12 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findByLauncher = registerForActivityResult(new FindByActivity.Contract(), result -> {
|
||||||
|
if (result != null) {
|
||||||
|
launch(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +172,12 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void launch(Recipient recipient) {
|
private void launch(Recipient recipient) {
|
||||||
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
|
launch(recipient.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void launch(RecipientId recipientId) {
|
||||||
|
Disposable disposable = ConversationIntents.createBuilder(this, recipientId, -1L)
|
||||||
.map(builder -> builder
|
.map(builder -> builder
|
||||||
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
||||||
.withDataUri(getIntent().getData())
|
.withDataUri(getIntent().getData())
|
||||||
@@ -234,6 +248,16 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByUsername() {
|
||||||
|
findByLauncher.launch(FindByMode.USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByPhoneNumber() {
|
||||||
|
findByLauncher.launch(FindByMode.PHONE_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView) {
|
public boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView) {
|
||||||
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
|
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
Log.i(TAG, "onAuthenticationSucceeded");
|
Log.i(TAG, "onAuthenticationSucceeded");
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.symbol_check_white_48);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
||||||
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -388,7 +388,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
public void onAuthenticationFailed() {
|
public void onAuthenticationFailed() {
|
||||||
Log.w(TAG, "onAuthenticationFailed()");
|
Log.w(TAG, "onAuthenticationFailed()");
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.symbol_x_white_48);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
|
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.preferences.MmsPreferencesActivity;
|
|
||||||
|
|
||||||
public class PromptMmsActivity extends PassphraseRequiredActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
|
||||||
setContentView(R.layout.prompt_apn_activity);
|
|
||||||
initializeResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
Button okButton = findViewById(R.id.ok_button);
|
|
||||||
Button cancelButton = findViewById(R.id.cancel_button);
|
|
||||||
|
|
||||||
okButton.setOnClickListener(v -> {
|
|
||||||
Intent intent = new Intent(PromptMmsActivity.this, MmsPreferencesActivity.class);
|
|
||||||
intent.putExtras(PromptMmsActivity.this.getIntent().getExtras());
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelButton.setOnClickListener(v -> finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -765,7 +765,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.RedPhone_number_not_registered)
|
.setTitle(R.string.RedPhone_number_not_registered)
|
||||||
.setIcon(R.drawable.ic_warning)
|
.setIcon(R.drawable.symbol_error_triangle_fill_24)
|
||||||
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||||
|
|||||||
@@ -1,319 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.os.ParcelCompat;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
|
||||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
|
||||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public abstract class Attachment implements Parcelable {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final String contentType;
|
|
||||||
private final int transferState;
|
|
||||||
private final long size;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String fileName;
|
|
||||||
|
|
||||||
private final int cdnNumber;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String location;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String key;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String relay;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final byte[] digest;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final byte[] incrementalDigest;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String fastPreflightId;
|
|
||||||
|
|
||||||
private final boolean voiceNote;
|
|
||||||
private final boolean borderless;
|
|
||||||
private final boolean videoGif;
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
private final boolean quote;
|
|
||||||
private final long uploadTimestamp;
|
|
||||||
private final int incrementalMacChunkSize;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String caption;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final StickerLocator stickerLocator;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final BlurHash blurHash;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final AudioHash audioHash;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final TransformProperties transformProperties;
|
|
||||||
|
|
||||||
public Attachment(@NonNull String contentType,
|
|
||||||
int transferState,
|
|
||||||
long size,
|
|
||||||
@Nullable String fileName,
|
|
||||||
int cdnNumber,
|
|
||||||
@Nullable String location,
|
|
||||||
@Nullable String key,
|
|
||||||
@Nullable String relay,
|
|
||||||
@Nullable byte[] digest,
|
|
||||||
@Nullable byte[] incrementalDigest,
|
|
||||||
@Nullable String fastPreflightId,
|
|
||||||
boolean voiceNote,
|
|
||||||
boolean borderless,
|
|
||||||
boolean videoGif,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int incrementalMacChunkSize,
|
|
||||||
boolean quote,
|
|
||||||
long uploadTimestamp,
|
|
||||||
@Nullable String caption,
|
|
||||||
@Nullable StickerLocator stickerLocator,
|
|
||||||
@Nullable BlurHash blurHash,
|
|
||||||
@Nullable AudioHash audioHash,
|
|
||||||
@Nullable TransformProperties transformProperties)
|
|
||||||
{
|
|
||||||
this.contentType = contentType;
|
|
||||||
this.transferState = transferState;
|
|
||||||
this.size = size;
|
|
||||||
this.fileName = fileName;
|
|
||||||
this.cdnNumber = cdnNumber;
|
|
||||||
this.location = location;
|
|
||||||
this.key = key;
|
|
||||||
this.relay = relay;
|
|
||||||
this.digest = digest;
|
|
||||||
this.incrementalDigest = incrementalDigest;
|
|
||||||
this.fastPreflightId = fastPreflightId;
|
|
||||||
this.voiceNote = voiceNote;
|
|
||||||
this.borderless = borderless;
|
|
||||||
this.videoGif = videoGif;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.incrementalMacChunkSize = incrementalMacChunkSize;
|
|
||||||
this.quote = quote;
|
|
||||||
this.uploadTimestamp = uploadTimestamp;
|
|
||||||
this.stickerLocator = stickerLocator;
|
|
||||||
this.caption = caption;
|
|
||||||
this.blurHash = blurHash;
|
|
||||||
this.audioHash = audioHash;
|
|
||||||
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Attachment(Parcel in) {
|
|
||||||
this.contentType = Objects.requireNonNull(in.readString());
|
|
||||||
this.transferState = in.readInt();
|
|
||||||
this.size = in.readLong();
|
|
||||||
this.fileName = in.readString();
|
|
||||||
this.cdnNumber = in.readInt();
|
|
||||||
this.location = in.readString();
|
|
||||||
this.key = in.readString();
|
|
||||||
this.relay = in.readString();
|
|
||||||
this.digest = ParcelUtil.readByteArray(in);
|
|
||||||
this.incrementalDigest = ParcelUtil.readByteArray(in);
|
|
||||||
this.fastPreflightId = in.readString();
|
|
||||||
this.voiceNote = ParcelUtil.readBoolean(in);
|
|
||||||
this.borderless = ParcelUtil.readBoolean(in);
|
|
||||||
this.videoGif = ParcelUtil.readBoolean(in);
|
|
||||||
this.width = in.readInt();
|
|
||||||
this.height = in.readInt();
|
|
||||||
this.incrementalMacChunkSize = in.readInt();
|
|
||||||
this.quote = ParcelUtil.readBoolean(in);
|
|
||||||
this.uploadTimestamp = in.readLong();
|
|
||||||
this.stickerLocator = ParcelCompat.readParcelable(in, StickerLocator.class.getClassLoader(), StickerLocator.class);
|
|
||||||
this.caption = in.readString();
|
|
||||||
this.blurHash = ParcelCompat.readParcelable(in, BlurHash.class.getClassLoader(), BlurHash.class);
|
|
||||||
this.audioHash = ParcelCompat.readParcelable(in, AudioHash.class.getClassLoader(), AudioHash.class);
|
|
||||||
this.transformProperties = Objects.requireNonNull(ParcelCompat.readParcelable(in, TransformProperties.class.getClassLoader(), TransformProperties.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
||||||
AttachmentCreator.writeSubclass(dest, this);
|
|
||||||
dest.writeString(contentType);
|
|
||||||
dest.writeInt(transferState);
|
|
||||||
dest.writeLong(size);
|
|
||||||
dest.writeString(fileName);
|
|
||||||
dest.writeInt(cdnNumber);
|
|
||||||
dest.writeString(location);
|
|
||||||
dest.writeString(key);
|
|
||||||
dest.writeString(relay);
|
|
||||||
ParcelUtil.writeByteArray(dest, digest);
|
|
||||||
ParcelUtil.writeByteArray(dest, incrementalDigest);
|
|
||||||
dest.writeString(fastPreflightId);
|
|
||||||
ParcelUtil.writeBoolean(dest, voiceNote);
|
|
||||||
ParcelUtil.writeBoolean(dest, borderless);
|
|
||||||
ParcelUtil.writeBoolean(dest, videoGif);
|
|
||||||
dest.writeInt(width);
|
|
||||||
dest.writeInt(height);
|
|
||||||
dest.writeInt(incrementalMacChunkSize);
|
|
||||||
ParcelUtil.writeBoolean(dest, quote);
|
|
||||||
dest.writeLong(uploadTimestamp);
|
|
||||||
dest.writeParcelable(stickerLocator, 0);
|
|
||||||
dest.writeString(caption);
|
|
||||||
dest.writeParcelable(blurHash, 0);
|
|
||||||
dest.writeParcelable(audioHash, 0);
|
|
||||||
dest.writeParcelable(transformProperties, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<Attachment> CREATOR = AttachmentCreator.INSTANCE;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public abstract Uri getUri();
|
|
||||||
|
|
||||||
public abstract @Nullable Uri getPublicUri();
|
|
||||||
|
|
||||||
public int getTransferState() {
|
|
||||||
return transferState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInProgress() {
|
|
||||||
return transferState != AttachmentTable.TRANSFER_PROGRESS_DONE &&
|
|
||||||
transferState != AttachmentTable.TRANSFER_PROGRESS_FAILED &&
|
|
||||||
transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPermanentlyFailed() {
|
|
||||||
return transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getSize() {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getFileName() {
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public String getContentType() {
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCdnNumber() {
|
|
||||||
return cdnNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getLocation() {
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getRelay() {
|
|
||||||
return relay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public byte[] getDigest() {
|
|
||||||
return digest;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public byte[] getIncrementalDigest() {
|
|
||||||
if (incrementalDigest != null && incrementalDigest.length > 0) {
|
|
||||||
return incrementalDigest;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getFastPreflightId() {
|
|
||||||
return fastPreflightId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVoiceNote() {
|
|
||||||
return voiceNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBorderless() {
|
|
||||||
return borderless;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVideoGif() {
|
|
||||||
return videoGif;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIncrementalMacChunkSize() {
|
|
||||||
return incrementalMacChunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isQuote() {
|
|
||||||
return quote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUploadTimestamp() {
|
|
||||||
return uploadTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSticker() {
|
|
||||||
return stickerLocator != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable StickerLocator getSticker() {
|
|
||||||
return stickerLocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable BlurHash getBlurHash() {
|
|
||||||
return blurHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable AudioHash getAudioHash() {
|
|
||||||
return audioHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getCaption() {
|
|
||||||
return caption;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull TransformProperties getTransformProperties() {
|
|
||||||
return transformProperties;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.core.os.ParcelCompat
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
|
import org.thoughtcrime.securesms.util.ParcelUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: We have to use our own Parcelable implementation because we need to do custom stuff to preserve
|
||||||
|
* subclass information.
|
||||||
|
*/
|
||||||
|
abstract class Attachment(
|
||||||
|
@JvmField
|
||||||
|
val contentType: String,
|
||||||
|
@JvmField
|
||||||
|
val transferState: Int,
|
||||||
|
@JvmField
|
||||||
|
val size: Long,
|
||||||
|
@JvmField
|
||||||
|
val fileName: String?,
|
||||||
|
@JvmField
|
||||||
|
val cdnNumber: Int,
|
||||||
|
@JvmField
|
||||||
|
val remoteLocation: String?,
|
||||||
|
@JvmField
|
||||||
|
val remoteKey: String?,
|
||||||
|
@JvmField
|
||||||
|
val remoteDigest: ByteArray?,
|
||||||
|
@JvmField
|
||||||
|
val incrementalDigest: ByteArray?,
|
||||||
|
@JvmField
|
||||||
|
val fastPreflightId: String?,
|
||||||
|
@JvmField
|
||||||
|
val voiceNote: Boolean,
|
||||||
|
@JvmField
|
||||||
|
val borderless: Boolean,
|
||||||
|
@JvmField
|
||||||
|
val videoGif: Boolean,
|
||||||
|
@JvmField
|
||||||
|
val width: Int,
|
||||||
|
@JvmField
|
||||||
|
val height: Int,
|
||||||
|
@JvmField
|
||||||
|
val incrementalMacChunkSize: Int,
|
||||||
|
@JvmField
|
||||||
|
val quote: Boolean,
|
||||||
|
@JvmField
|
||||||
|
val uploadTimestamp: Long,
|
||||||
|
@JvmField
|
||||||
|
val caption: String?,
|
||||||
|
@JvmField
|
||||||
|
val stickerLocator: StickerLocator?,
|
||||||
|
@JvmField
|
||||||
|
val blurHash: BlurHash?,
|
||||||
|
@JvmField
|
||||||
|
val audioHash: AudioHash?,
|
||||||
|
@JvmField
|
||||||
|
val transformProperties: TransformProperties?
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
abstract val uri: Uri?
|
||||||
|
abstract val publicUri: Uri?
|
||||||
|
|
||||||
|
protected constructor(parcel: Parcel) : this(
|
||||||
|
contentType = parcel.readString()!!,
|
||||||
|
transferState = parcel.readInt(),
|
||||||
|
size = parcel.readLong(),
|
||||||
|
fileName = parcel.readString(),
|
||||||
|
cdnNumber = parcel.readInt(),
|
||||||
|
remoteLocation = parcel.readString(),
|
||||||
|
remoteKey = parcel.readString(),
|
||||||
|
remoteDigest = ParcelUtil.readByteArray(parcel),
|
||||||
|
incrementalDigest = ParcelUtil.readByteArray(parcel),
|
||||||
|
fastPreflightId = parcel.readString(),
|
||||||
|
voiceNote = ParcelUtil.readBoolean(parcel),
|
||||||
|
borderless = ParcelUtil.readBoolean(parcel),
|
||||||
|
videoGif = ParcelUtil.readBoolean(parcel),
|
||||||
|
width = parcel.readInt(),
|
||||||
|
height = parcel.readInt(),
|
||||||
|
incrementalMacChunkSize = parcel.readInt(),
|
||||||
|
quote = ParcelUtil.readBoolean(parcel),
|
||||||
|
uploadTimestamp = parcel.readLong(),
|
||||||
|
caption = parcel.readString(),
|
||||||
|
stickerLocator = ParcelCompat.readParcelable(parcel, StickerLocator::class.java.classLoader, StickerLocator::class.java),
|
||||||
|
blurHash = ParcelCompat.readParcelable(parcel, BlurHash::class.java.classLoader, BlurHash::class.java),
|
||||||
|
audioHash = ParcelCompat.readParcelable(parcel, AudioHash::class.java.classLoader, AudioHash::class.java),
|
||||||
|
transformProperties = ParcelCompat.readParcelable(parcel, TransformProperties::class.java.classLoader, TransformProperties::class.java)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
AttachmentCreator.writeSubclass(dest, this)
|
||||||
|
dest.writeString(contentType)
|
||||||
|
dest.writeInt(transferState)
|
||||||
|
dest.writeLong(size)
|
||||||
|
dest.writeString(fileName)
|
||||||
|
dest.writeInt(cdnNumber)
|
||||||
|
dest.writeString(remoteLocation)
|
||||||
|
dest.writeString(remoteKey)
|
||||||
|
ParcelUtil.writeByteArray(dest, remoteDigest)
|
||||||
|
ParcelUtil.writeByteArray(dest, incrementalDigest)
|
||||||
|
dest.writeString(fastPreflightId)
|
||||||
|
ParcelUtil.writeBoolean(dest, voiceNote)
|
||||||
|
ParcelUtil.writeBoolean(dest, borderless)
|
||||||
|
ParcelUtil.writeBoolean(dest, videoGif)
|
||||||
|
dest.writeInt(width)
|
||||||
|
dest.writeInt(height)
|
||||||
|
dest.writeInt(incrementalMacChunkSize)
|
||||||
|
ParcelUtil.writeBoolean(dest, quote)
|
||||||
|
dest.writeLong(uploadTimestamp)
|
||||||
|
dest.writeString(caption)
|
||||||
|
dest.writeParcelable(stickerLocator, 0)
|
||||||
|
dest.writeParcelable(blurHash, 0)
|
||||||
|
dest.writeParcelable(audioHash, 0)
|
||||||
|
dest.writeParcelable(transformProperties, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val isInProgress: Boolean
|
||||||
|
get() = transferState != AttachmentTable.TRANSFER_PROGRESS_DONE && transferState != AttachmentTable.TRANSFER_PROGRESS_FAILED && transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE
|
||||||
|
|
||||||
|
val isPermanentlyFailed: Boolean
|
||||||
|
get() = transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE
|
||||||
|
|
||||||
|
val isSticker: Boolean
|
||||||
|
get() = stickerLocator != null
|
||||||
|
|
||||||
|
fun getIncrementalDigest(): ByteArray? {
|
||||||
|
return if (incrementalDigest != null && incrementalDigest.size > 0) {
|
||||||
|
incrementalDigest
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<Attachment> = AttachmentCreator
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ import android.os.Parcelable
|
|||||||
object AttachmentCreator : Parcelable.Creator<Attachment> {
|
object AttachmentCreator : Parcelable.Creator<Attachment> {
|
||||||
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
|
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
|
||||||
DATABASE(DatabaseAttachment::class.java, "database"),
|
DATABASE(DatabaseAttachment::class.java, "database"),
|
||||||
MMS_NOTIFICATION(MmsNotificationAttachment::class.java, "mms_notification"),
|
|
||||||
POINTER(PointerAttachment::class.java, "pointer"),
|
POINTER(PointerAttachment::class.java, "pointer"),
|
||||||
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
|
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
|
||||||
URI(UriAttachment::class.java, "uri")
|
URI(UriAttachment::class.java, "uri")
|
||||||
@@ -32,7 +31,6 @@ object AttachmentCreator : Parcelable.Creator<Attachment> {
|
|||||||
|
|
||||||
return when (Subclass.values().first { rawCode == it.code }) {
|
return when (Subclass.values().first { rawCode == it.code }) {
|
||||||
Subclass.DATABASE -> DatabaseAttachment(source)
|
Subclass.DATABASE -> DatabaseAttachment(source)
|
||||||
Subclass.MMS_NOTIFICATION -> MmsNotificationAttachment(source)
|
|
||||||
Subclass.POINTER -> PointerAttachment(source)
|
Subclass.POINTER -> PointerAttachment(source)
|
||||||
Subclass.TOMBSTONE -> TombstoneAttachment(source)
|
Subclass.TOMBSTONE -> TombstoneAttachment(source)
|
||||||
Subclass.URI -> UriAttachment(source)
|
Subclass.URI -> UriAttachment(source)
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
public class AttachmentId implements Parcelable {
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private final long rowId;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private final long uniqueId;
|
|
||||||
|
|
||||||
public AttachmentId(@JsonProperty("rowId") long rowId, @JsonProperty("uniqueId") long uniqueId) {
|
|
||||||
this.rowId = rowId;
|
|
||||||
this.uniqueId = uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttachmentId(Parcel in) {
|
|
||||||
this.rowId = in.readLong();
|
|
||||||
this.uniqueId = in.readLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRowId() {
|
|
||||||
return rowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUniqueId() {
|
|
||||||
return uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] toStrings() {
|
|
||||||
return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)};
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String toString() {
|
|
||||||
return "AttachmentId::(" + rowId + ", " + uniqueId + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
return rowId >= 0 && uniqueId >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
AttachmentId attachmentId = (AttachmentId)o;
|
|
||||||
|
|
||||||
if (rowId != attachmentId.rowId) return false;
|
|
||||||
return uniqueId == attachmentId.uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Util.hashCode(rowId, uniqueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeLong(rowId);
|
|
||||||
dest.writeLong(uniqueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<AttachmentId> CREATOR = new Creator<AttachmentId>() {
|
|
||||||
@Override
|
|
||||||
public AttachmentId createFromParcel(Parcel in) {
|
|
||||||
return new AttachmentId(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AttachmentId[] newArray(int size) {
|
|
||||||
return new AttachmentId[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AttachmentId(
|
||||||
|
@JsonProperty("rowId")
|
||||||
|
@JvmField
|
||||||
|
val id: Long
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
val isValid: Boolean
|
||||||
|
get() = id >= 0
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "AttachmentId::$id"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.os.ParcelCompat;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
|
||||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
public class DatabaseAttachment extends Attachment {
|
|
||||||
|
|
||||||
private final AttachmentId attachmentId;
|
|
||||||
private final long mmsId;
|
|
||||||
private final boolean hasData;
|
|
||||||
private final boolean hasThumbnail;
|
|
||||||
private final int displayOrder;
|
|
||||||
|
|
||||||
public DatabaseAttachment(AttachmentId attachmentId,
|
|
||||||
long mmsId,
|
|
||||||
boolean hasData,
|
|
||||||
boolean hasThumbnail,
|
|
||||||
String contentType,
|
|
||||||
int transferProgress,
|
|
||||||
long size,
|
|
||||||
String fileName,
|
|
||||||
int cdnNumber,
|
|
||||||
String location,
|
|
||||||
String key,
|
|
||||||
String relay,
|
|
||||||
byte[] digest,
|
|
||||||
byte[] incrementalDigest,
|
|
||||||
int incrementalMacChunkSize,
|
|
||||||
String fastPreflightId,
|
|
||||||
boolean voiceNote,
|
|
||||||
boolean borderless,
|
|
||||||
boolean videoGif,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
boolean quote,
|
|
||||||
@Nullable String caption,
|
|
||||||
@Nullable StickerLocator stickerLocator,
|
|
||||||
@Nullable BlurHash blurHash,
|
|
||||||
@Nullable AudioHash audioHash,
|
|
||||||
@Nullable TransformProperties transformProperties,
|
|
||||||
int displayOrder,
|
|
||||||
long uploadTimestamp)
|
|
||||||
{
|
|
||||||
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
|
||||||
this.attachmentId = attachmentId;
|
|
||||||
this.hasData = hasData;
|
|
||||||
this.hasThumbnail = hasThumbnail;
|
|
||||||
this.mmsId = mmsId;
|
|
||||||
this.displayOrder = displayOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DatabaseAttachment(Parcel in) {
|
|
||||||
super(in);
|
|
||||||
this.attachmentId = ParcelCompat.readParcelable(in, AttachmentId.class.getClassLoader(), AttachmentId.class);
|
|
||||||
this.hasData = ParcelUtil.readBoolean(in);
|
|
||||||
this.hasThumbnail = ParcelUtil.readBoolean(in);
|
|
||||||
this.mmsId = in.readLong();
|
|
||||||
this.displayOrder = in.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
dest.writeParcelable(attachmentId, 0);
|
|
||||||
ParcelUtil.writeBoolean(dest, hasData);
|
|
||||||
ParcelUtil.writeBoolean(dest, hasThumbnail);
|
|
||||||
dest.writeLong(mmsId);
|
|
||||||
dest.writeInt(displayOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public Uri getUri() {
|
|
||||||
if (hasData || (FeatureFlags.instantVideoPlayback() && getIncrementalDigest() != null)) {
|
|
||||||
return PartAuthority.getAttachmentDataUri(attachmentId);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getPublicUri() {
|
|
||||||
if (hasData) {
|
|
||||||
return PartAuthority.getAttachmentPublicUri(getUri());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AttachmentId getAttachmentId() {
|
|
||||||
return attachmentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDisplayOrder() {
|
|
||||||
return displayOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other != null &&
|
|
||||||
other instanceof DatabaseAttachment &&
|
|
||||||
((DatabaseAttachment) other).attachmentId.equals(this.attachmentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return attachmentId.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMmsId() {
|
|
||||||
return mmsId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasData() {
|
|
||||||
return hasData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasThumbnail() {
|
|
||||||
return hasThumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DisplayOrderComparator implements Comparator<DatabaseAttachment> {
|
|
||||||
@Override
|
|
||||||
public int compare(DatabaseAttachment lhs, DatabaseAttachment rhs) {
|
|
||||||
return Integer.compare(lhs.getDisplayOrder(), rhs.getDisplayOrder());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import androidx.core.os.ParcelCompat
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
|
||||||
|
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||||
|
import org.thoughtcrime.securesms.util.ParcelUtil
|
||||||
|
|
||||||
|
class DatabaseAttachment : Attachment {
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val attachmentId: AttachmentId
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val mmsId: Long
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val hasData: Boolean
|
||||||
|
|
||||||
|
private val hasThumbnail: Boolean
|
||||||
|
val displayOrder: Int
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
attachmentId: AttachmentId,
|
||||||
|
mmsId: Long,
|
||||||
|
hasData: Boolean,
|
||||||
|
hasThumbnail: Boolean,
|
||||||
|
contentType: String?,
|
||||||
|
transferProgress: Int,
|
||||||
|
size: Long,
|
||||||
|
fileName: String?,
|
||||||
|
cdnNumber: Int,
|
||||||
|
location: String?,
|
||||||
|
key: String?,
|
||||||
|
digest: ByteArray?,
|
||||||
|
incrementalDigest: ByteArray?,
|
||||||
|
incrementalMacChunkSize: Int,
|
||||||
|
fastPreflightId: String?,
|
||||||
|
voiceNote: Boolean,
|
||||||
|
borderless: Boolean,
|
||||||
|
videoGif: Boolean,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
quote: Boolean,
|
||||||
|
caption: String?,
|
||||||
|
stickerLocator: StickerLocator?,
|
||||||
|
blurHash: BlurHash?,
|
||||||
|
audioHash: AudioHash?,
|
||||||
|
transformProperties: TransformProperties?,
|
||||||
|
displayOrder: Int,
|
||||||
|
uploadTimestamp: Long
|
||||||
|
) : super(
|
||||||
|
contentType = contentType!!,
|
||||||
|
transferState = transferProgress,
|
||||||
|
size = size,
|
||||||
|
fileName = fileName,
|
||||||
|
cdnNumber = cdnNumber,
|
||||||
|
remoteLocation = location,
|
||||||
|
remoteKey = key,
|
||||||
|
remoteDigest = digest,
|
||||||
|
incrementalDigest = incrementalDigest,
|
||||||
|
fastPreflightId = fastPreflightId,
|
||||||
|
voiceNote = voiceNote,
|
||||||
|
borderless = borderless,
|
||||||
|
videoGif = videoGif, width = width,
|
||||||
|
height = height,
|
||||||
|
incrementalMacChunkSize = incrementalMacChunkSize,
|
||||||
|
quote = quote,
|
||||||
|
uploadTimestamp = uploadTimestamp,
|
||||||
|
caption = caption,
|
||||||
|
stickerLocator = stickerLocator,
|
||||||
|
blurHash = blurHash,
|
||||||
|
audioHash = audioHash,
|
||||||
|
transformProperties = transformProperties
|
||||||
|
) {
|
||||||
|
this.attachmentId = attachmentId
|
||||||
|
this.mmsId = mmsId
|
||||||
|
this.hasData = hasData
|
||||||
|
this.hasThumbnail = hasThumbnail
|
||||||
|
this.displayOrder = displayOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
|
attachmentId = ParcelCompat.readParcelable(parcel, AttachmentId::class.java.classLoader, AttachmentId::class.java)!!
|
||||||
|
hasData = ParcelUtil.readBoolean(parcel)
|
||||||
|
hasThumbnail = ParcelUtil.readBoolean(parcel)
|
||||||
|
mmsId = parcel.readLong()
|
||||||
|
displayOrder = parcel.readInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
super.writeToParcel(dest, flags)
|
||||||
|
dest.writeParcelable(attachmentId, 0)
|
||||||
|
ParcelUtil.writeBoolean(dest, hasData)
|
||||||
|
ParcelUtil.writeBoolean(dest, hasThumbnail)
|
||||||
|
dest.writeLong(mmsId)
|
||||||
|
dest.writeInt(displayOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val uri: Uri?
|
||||||
|
get() = if (hasData || FeatureFlags.instantVideoPlayback() && getIncrementalDigest() != null) {
|
||||||
|
PartAuthority.getAttachmentDataUri(attachmentId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override val publicUri: Uri?
|
||||||
|
get() = if (hasData) {
|
||||||
|
PartAuthority.getAttachmentPublicUri(uri)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other != null &&
|
||||||
|
other is DatabaseAttachment && other.attachmentId == attachmentId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return attachmentId.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DisplayOrderComparator : Comparator<DatabaseAttachment> {
|
||||||
|
override fun compare(lhs: DatabaseAttachment, rhs: DatabaseAttachment): Int {
|
||||||
|
return lhs.displayOrder.compareTo(rhs.displayOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
|
||||||
import org.thoughtcrime.securesms.database.MessageTable;
|
|
||||||
|
|
||||||
public class MmsNotificationAttachment extends Attachment {
|
|
||||||
|
|
||||||
public MmsNotificationAttachment(int status, long size) {
|
|
||||||
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, false, 0, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MmsNotificationAttachment(Parcel in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getPublicUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getTransferStateFromStatus(int status) {
|
|
||||||
if (status == MessageTable.MmsStatus.DOWNLOAD_INITIALIZED ||
|
|
||||||
status == MessageTable.MmsStatus.DOWNLOAD_NO_CONNECTIVITY)
|
|
||||||
{
|
|
||||||
return AttachmentTable.TRANSFER_PROGRESS_PENDING;
|
|
||||||
} else if (status == MessageTable.MmsStatus.DOWNLOAD_CONNECTING) {
|
|
||||||
return AttachmentTable.TRANSFER_PROGRESS_STARTED;
|
|
||||||
} else {
|
|
||||||
return AttachmentTable.TRANSFER_PROGRESS_FAILED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
|
||||||
import org.signal.core.util.Base64;
|
|
||||||
import org.whispersystems.signalservice.api.InvalidMessageStructureException;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
|
||||||
import org.whispersystems.signalservice.api.util.AttachmentPointerUtil;
|
|
||||||
import org.whispersystems.signalservice.internal.push.DataMessage;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class PointerAttachment extends Attachment {
|
|
||||||
|
|
||||||
private PointerAttachment(@NonNull String contentType,
|
|
||||||
int transferState,
|
|
||||||
long size,
|
|
||||||
@Nullable String fileName,
|
|
||||||
int cdnNumber,
|
|
||||||
@NonNull String location,
|
|
||||||
@Nullable String key,
|
|
||||||
@Nullable String relay,
|
|
||||||
@Nullable byte[] digest,
|
|
||||||
@Nullable byte[] incrementalDigest,
|
|
||||||
int incrementalMacChunkSize,
|
|
||||||
@Nullable String fastPreflightId,
|
|
||||||
boolean voiceNote,
|
|
||||||
boolean borderless,
|
|
||||||
boolean videoGif,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
long uploadTimestamp,
|
|
||||||
@Nullable String caption,
|
|
||||||
@Nullable StickerLocator stickerLocator,
|
|
||||||
@Nullable BlurHash blurHash)
|
|
||||||
{
|
|
||||||
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PointerAttachment(Parcel in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getPublicUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Attachment> forPointers(Optional<List<SignalServiceAttachment>> pointers) {
|
|
||||||
List<Attachment> results = new LinkedList<>();
|
|
||||||
|
|
||||||
if (pointers.isPresent()) {
|
|
||||||
for (SignalServiceAttachment pointer : pointers.get()) {
|
|
||||||
Optional<Attachment> result = forPointer(Optional.of(pointer));
|
|
||||||
|
|
||||||
if (result.isPresent()) {
|
|
||||||
results.add(result.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Attachment> forPointers(@Nullable List<SignalServiceDataMessage.Quote.QuotedAttachment> pointers) {
|
|
||||||
List<Attachment> results = new LinkedList<>();
|
|
||||||
|
|
||||||
if (pointers != null) {
|
|
||||||
for (SignalServiceDataMessage.Quote.QuotedAttachment pointer : pointers) {
|
|
||||||
Optional<Attachment> result = forPointer(pointer);
|
|
||||||
|
|
||||||
if (result.isPresent()) {
|
|
||||||
results.add(result.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer) {
|
|
||||||
return forPointer(pointer, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator) {
|
|
||||||
return forPointer(pointer, stickerLocator, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) {
|
|
||||||
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.empty();
|
|
||||||
|
|
||||||
String encodedKey = null;
|
|
||||||
|
|
||||||
if (pointer.get().asPointer().getKey() != null) {
|
|
||||||
encodedKey = Base64.encodeWithPadding(pointer.get().asPointer().getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
|
||||||
AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
|
||||||
pointer.get().asPointer().getSize().orElse(0),
|
|
||||||
pointer.get().asPointer().getFileName().orElse(null),
|
|
||||||
pointer.get().asPointer().getCdnNumber(),
|
|
||||||
pointer.get().asPointer().getRemoteId().toString(),
|
|
||||||
encodedKey,
|
|
||||||
null,
|
|
||||||
pointer.get().asPointer().getDigest().orElse(null),
|
|
||||||
pointer.get().asPointer().getIncrementalDigest().orElse(null),
|
|
||||||
pointer.get().asPointer().getIncrementalMacChunkSize(),
|
|
||||||
fastPreflightId,
|
|
||||||
pointer.get().asPointer().getVoiceNote(),
|
|
||||||
pointer.get().asPointer().isBorderless(),
|
|
||||||
pointer.get().asPointer().isGif(),
|
|
||||||
pointer.get().asPointer().getWidth(),
|
|
||||||
pointer.get().asPointer().getHeight(),
|
|
||||||
pointer.get().asPointer().getUploadTimestamp(),
|
|
||||||
pointer.get().asPointer().getCaption().orElse(null),
|
|
||||||
stickerLocator,
|
|
||||||
BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orElse(null))));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(SignalServiceDataMessage.Quote.QuotedAttachment pointer) {
|
|
||||||
SignalServiceAttachment thumbnail = pointer.getThumbnail();
|
|
||||||
|
|
||||||
return Optional.of(new PointerAttachment(pointer.getContentType(),
|
|
||||||
AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
|
|
||||||
pointer.getFileName(),
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
|
||||||
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeWithPadding(thumbnail.asPointer().getKey()) : null,
|
|
||||||
null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getCaption().orElse(null) : null,
|
|
||||||
null,
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(DataMessage.Quote.QuotedAttachment quotedAttachment) {
|
|
||||||
SignalServiceAttachment thumbnail;
|
|
||||||
try {
|
|
||||||
thumbnail = quotedAttachment.thumbnail != null ? AttachmentPointerUtil.createSignalAttachmentPointer(quotedAttachment.thumbnail) : null;
|
|
||||||
} catch (InvalidMessageStructureException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(new PointerAttachment(quotedAttachment.contentType,
|
|
||||||
AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
|
|
||||||
quotedAttachment.fileName,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
|
||||||
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeWithPadding(thumbnail.asPointer().getKey()) : null,
|
|
||||||
null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
|
||||||
thumbnail != null ? thumbnail.asPointer().getCaption().orElse(null) : null,
|
|
||||||
null,
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import org.signal.core.util.Base64.encodeWithPadding
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
|
import org.whispersystems.signalservice.api.InvalidMessageStructureException
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
|
import org.whispersystems.signalservice.api.util.AttachmentPointerUtil
|
||||||
|
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
class PointerAttachment : Attachment {
|
||||||
|
private constructor(
|
||||||
|
contentType: String,
|
||||||
|
transferState: Int,
|
||||||
|
size: Long,
|
||||||
|
fileName: String?,
|
||||||
|
cdnNumber: Int,
|
||||||
|
location: String,
|
||||||
|
key: String?,
|
||||||
|
digest: ByteArray?,
|
||||||
|
incrementalDigest: ByteArray?,
|
||||||
|
incrementalMacChunkSize: Int,
|
||||||
|
fastPreflightId: String?,
|
||||||
|
voiceNote: Boolean,
|
||||||
|
borderless: Boolean,
|
||||||
|
videoGif: Boolean,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
uploadTimestamp: Long,
|
||||||
|
caption: String?,
|
||||||
|
stickerLocator: StickerLocator?,
|
||||||
|
blurHash: BlurHash?
|
||||||
|
) : super(
|
||||||
|
contentType = contentType,
|
||||||
|
transferState = transferState,
|
||||||
|
size = size,
|
||||||
|
fileName = fileName,
|
||||||
|
cdnNumber = cdnNumber,
|
||||||
|
remoteLocation = location,
|
||||||
|
remoteKey = key,
|
||||||
|
remoteDigest = digest,
|
||||||
|
incrementalDigest = incrementalDigest,
|
||||||
|
fastPreflightId = fastPreflightId,
|
||||||
|
voiceNote = voiceNote,
|
||||||
|
borderless = borderless,
|
||||||
|
videoGif = videoGif,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
incrementalMacChunkSize = incrementalMacChunkSize,
|
||||||
|
quote = false,
|
||||||
|
uploadTimestamp = uploadTimestamp,
|
||||||
|
caption = caption,
|
||||||
|
stickerLocator = stickerLocator,
|
||||||
|
blurHash = blurHash,
|
||||||
|
audioHash = null,
|
||||||
|
transformProperties = null
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : super(parcel)
|
||||||
|
|
||||||
|
override val uri: Uri? = null
|
||||||
|
override val publicUri: Uri? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun forPointers(pointers: Optional<List<SignalServiceAttachment>>): List<Attachment> {
|
||||||
|
if (!pointers.isPresent) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointers.get()
|
||||||
|
.map { forPointer(Optional.ofNullable(it)) }
|
||||||
|
.filter { it.isPresent }
|
||||||
|
.map { it.get() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun forPointer(pointer: Optional<SignalServiceAttachment>, stickerLocator: StickerLocator? = null, fastPreflightId: String? = null): Optional<Attachment> {
|
||||||
|
if (!pointer.isPresent || !pointer.get().isPointer) {
|
||||||
|
return Optional.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val encodedKey: String? = if (pointer.get().asPointer().key != null) {
|
||||||
|
encodeWithPadding(pointer.get().asPointer().key)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(
|
||||||
|
PointerAttachment(
|
||||||
|
contentType = pointer.get().contentType,
|
||||||
|
transferState = AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||||
|
size = pointer.get().asPointer().size.orElse(0).toLong(),
|
||||||
|
fileName = pointer.get().asPointer().fileName.orElse(null),
|
||||||
|
cdnNumber = pointer.get().asPointer().cdnNumber,
|
||||||
|
location = pointer.get().asPointer().remoteId.toString(),
|
||||||
|
key = encodedKey,
|
||||||
|
digest = pointer.get().asPointer().digest.orElse(null),
|
||||||
|
incrementalDigest = pointer.get().asPointer().incrementalDigest.orElse(null),
|
||||||
|
incrementalMacChunkSize = pointer.get().asPointer().incrementalMacChunkSize,
|
||||||
|
fastPreflightId = fastPreflightId,
|
||||||
|
voiceNote = pointer.get().asPointer().voiceNote,
|
||||||
|
borderless = pointer.get().asPointer().isBorderless,
|
||||||
|
videoGif = pointer.get().asPointer().isGif,
|
||||||
|
width = pointer.get().asPointer().width,
|
||||||
|
height = pointer.get().asPointer().height,
|
||||||
|
uploadTimestamp = pointer.get().asPointer().uploadTimestamp,
|
||||||
|
caption = pointer.get().asPointer().caption.orElse(null),
|
||||||
|
stickerLocator = stickerLocator,
|
||||||
|
blurHash = BlurHash.parseOrNull(pointer.get().asPointer().blurHash.orElse(null))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forPointer(pointer: SignalServiceDataMessage.Quote.QuotedAttachment): Optional<Attachment> {
|
||||||
|
val thumbnail = pointer.thumbnail
|
||||||
|
|
||||||
|
return Optional.of(
|
||||||
|
PointerAttachment(
|
||||||
|
contentType = pointer.contentType,
|
||||||
|
transferState = AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||||
|
size = (if (thumbnail != null) thumbnail.asPointer().size.orElse(0) else 0).toLong(),
|
||||||
|
fileName = pointer.fileName,
|
||||||
|
cdnNumber = thumbnail?.asPointer()?.cdnNumber ?: 0,
|
||||||
|
location = thumbnail?.asPointer()?.remoteId?.toString() ?: "0",
|
||||||
|
key = if (thumbnail != null && thumbnail.asPointer().key != null) encodeWithPadding(thumbnail.asPointer().key) else null,
|
||||||
|
digest = thumbnail?.asPointer()?.digest?.orElse(null),
|
||||||
|
incrementalDigest = thumbnail?.asPointer()?.incrementalDigest?.orElse(null),
|
||||||
|
incrementalMacChunkSize = thumbnail?.asPointer()?.incrementalMacChunkSize ?: 0,
|
||||||
|
fastPreflightId = null,
|
||||||
|
voiceNote = false,
|
||||||
|
borderless = false,
|
||||||
|
videoGif = false,
|
||||||
|
width = thumbnail?.asPointer()?.width ?: 0,
|
||||||
|
height = thumbnail?.asPointer()?.height ?: 0,
|
||||||
|
uploadTimestamp = thumbnail?.asPointer()?.uploadTimestamp ?: 0,
|
||||||
|
caption = thumbnail?.asPointer()?.caption?.orElse(null),
|
||||||
|
stickerLocator = null,
|
||||||
|
blurHash = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forPointer(quotedAttachment: DataMessage.Quote.QuotedAttachment): Optional<Attachment> {
|
||||||
|
val thumbnail: SignalServiceAttachment? = try {
|
||||||
|
if (quotedAttachment.thumbnail != null) {
|
||||||
|
AttachmentPointerUtil.createSignalAttachmentPointer(quotedAttachment.thumbnail)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: InvalidMessageStructureException) {
|
||||||
|
return Optional.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(
|
||||||
|
PointerAttachment(
|
||||||
|
contentType = quotedAttachment.contentType!!,
|
||||||
|
transferState = AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||||
|
size = (if (thumbnail != null) thumbnail.asPointer().size.orElse(0) else 0).toLong(),
|
||||||
|
fileName = quotedAttachment.fileName,
|
||||||
|
cdnNumber = thumbnail?.asPointer()?.cdnNumber ?: 0,
|
||||||
|
location = thumbnail?.asPointer()?.remoteId?.toString() ?: "0",
|
||||||
|
key = if (thumbnail != null && thumbnail.asPointer().key != null) encodeWithPadding(thumbnail.asPointer().key) else null,
|
||||||
|
digest = thumbnail?.asPointer()?.digest?.orElse(null),
|
||||||
|
incrementalDigest = thumbnail?.asPointer()?.incrementalDigest?.orElse(null),
|
||||||
|
incrementalMacChunkSize = thumbnail?.asPointer()?.incrementalMacChunkSize ?: 0,
|
||||||
|
fastPreflightId = null,
|
||||||
|
voiceNote = false,
|
||||||
|
borderless = false,
|
||||||
|
videoGif = false,
|
||||||
|
width = thumbnail?.asPointer()?.width ?: 0,
|
||||||
|
height = thumbnail?.asPointer()?.height ?: 0,
|
||||||
|
uploadTimestamp = thumbnail?.asPointer()?.uploadTimestamp ?: 0,
|
||||||
|
caption = thumbnail?.asPointer()?.caption?.orElse(null),
|
||||||
|
stickerLocator = null,
|
||||||
|
blurHash = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An attachment that represents where an attachment used to be. Useful when you need to know that
|
|
||||||
* a message had an attachment and some metadata about it (like the contentType), even though the
|
|
||||||
* underlying media no longer exists. An example usecase would be view-once messages, so that we can
|
|
||||||
* quote them and know their contentType even though the media has been deleted.
|
|
||||||
*/
|
|
||||||
public class TombstoneAttachment extends Attachment {
|
|
||||||
|
|
||||||
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
|
|
||||||
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, quote, 0, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TombstoneAttachment(Parcel in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getPublicUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment that represents where an attachment used to be. Useful when you need to know that
|
||||||
|
* a message had an attachment and some metadata about it (like the contentType), even though the
|
||||||
|
* underlying media no longer exists. An example usecase would be view-once messages, so that we can
|
||||||
|
* quote them and know their contentType even though the media has been deleted.
|
||||||
|
*/
|
||||||
|
class TombstoneAttachment : Attachment {
|
||||||
|
constructor(contentType: String, quote: Boolean) : super(
|
||||||
|
contentType = contentType,
|
||||||
|
quote = quote,
|
||||||
|
transferState = AttachmentTable.TRANSFER_PROGRESS_DONE,
|
||||||
|
size = 0,
|
||||||
|
fileName = null,
|
||||||
|
cdnNumber = 0,
|
||||||
|
remoteLocation = null,
|
||||||
|
remoteKey = null,
|
||||||
|
remoteDigest = null,
|
||||||
|
incrementalDigest = null,
|
||||||
|
fastPreflightId = null,
|
||||||
|
voiceNote = false,
|
||||||
|
borderless = false,
|
||||||
|
videoGif = false,
|
||||||
|
width = 0,
|
||||||
|
height = 0,
|
||||||
|
incrementalMacChunkSize = 0,
|
||||||
|
uploadTimestamp = 0,
|
||||||
|
caption = null,
|
||||||
|
stickerLocator = null,
|
||||||
|
blurHash = null,
|
||||||
|
audioHash = null,
|
||||||
|
transformProperties = null
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : super(parcel)
|
||||||
|
|
||||||
|
override val uri: Uri? = null
|
||||||
|
override val publicUri: Uri? = null
|
||||||
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.attachments;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.os.ParcelCompat;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
|
||||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class UriAttachment extends Attachment {
|
|
||||||
|
|
||||||
private final @NonNull Uri dataUri;
|
|
||||||
|
|
||||||
public UriAttachment(@NonNull Uri uri,
|
|
||||||
@NonNull String contentType,
|
|
||||||
int transferState,
|
|
||||||
long size,
|
|
||||||
@Nullable String fileName,
|
|
||||||
boolean voiceNote,
|
|
||||||
boolean borderless,
|
|
||||||
boolean videoGif,
|
|
||||||
boolean quote,
|
|
||||||
@Nullable String caption,
|
|
||||||
@Nullable StickerLocator stickerLocator,
|
|
||||||
@Nullable BlurHash blurHash,
|
|
||||||
@Nullable AudioHash audioHash,
|
|
||||||
@Nullable TransformProperties transformProperties)
|
|
||||||
{
|
|
||||||
this(uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, borderless, videoGif, quote, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UriAttachment(@NonNull Uri dataUri,
|
|
||||||
@NonNull String contentType,
|
|
||||||
int transferState,
|
|
||||||
long size,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
@Nullable String fileName,
|
|
||||||
@Nullable String fastPreflightId,
|
|
||||||
boolean voiceNote,
|
|
||||||
boolean borderless,
|
|
||||||
boolean videoGif,
|
|
||||||
boolean quote,
|
|
||||||
@Nullable String caption,
|
|
||||||
@Nullable StickerLocator stickerLocator,
|
|
||||||
@Nullable BlurHash blurHash,
|
|
||||||
@Nullable AudioHash audioHash,
|
|
||||||
@Nullable TransformProperties transformProperties)
|
|
||||||
{
|
|
||||||
super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, 0, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
|
||||||
this.dataUri = Objects.requireNonNull(dataUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected UriAttachment(Parcel in) {
|
|
||||||
super(in);
|
|
||||||
this.dataUri = Objects.requireNonNull(ParcelCompat.readParcelable(in, Uri.class.getClassLoader(), Uri.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
dest.writeParcelable(dataUri, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Uri getUri() {
|
|
||||||
return dataUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Uri getPublicUri() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other != null && other instanceof UriAttachment && ((UriAttachment) other).dataUri.equals(this.dataUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return dataUri.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import androidx.core.os.ParcelCompat
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||||
|
import java.util.Objects
|
||||||
|
|
||||||
|
class UriAttachment : Attachment {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
uri: Uri,
|
||||||
|
contentType: String,
|
||||||
|
transferState: Int,
|
||||||
|
size: Long,
|
||||||
|
fileName: String?,
|
||||||
|
voiceNote: Boolean,
|
||||||
|
borderless: Boolean,
|
||||||
|
videoGif: Boolean,
|
||||||
|
quote: Boolean,
|
||||||
|
caption: String?,
|
||||||
|
stickerLocator: StickerLocator?,
|
||||||
|
blurHash: BlurHash?,
|
||||||
|
audioHash: AudioHash?,
|
||||||
|
transformProperties: TransformProperties?
|
||||||
|
) : this(
|
||||||
|
dataUri = uri,
|
||||||
|
contentType = contentType,
|
||||||
|
transferState = transferState,
|
||||||
|
size = size,
|
||||||
|
width = 0,
|
||||||
|
height = 0,
|
||||||
|
fileName = fileName,
|
||||||
|
fastPreflightId = null,
|
||||||
|
voiceNote = voiceNote,
|
||||||
|
borderless = borderless,
|
||||||
|
videoGif = videoGif,
|
||||||
|
quote = quote,
|
||||||
|
caption = caption,
|
||||||
|
stickerLocator = stickerLocator,
|
||||||
|
blurHash = blurHash,
|
||||||
|
audioHash = audioHash,
|
||||||
|
transformProperties = transformProperties
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
dataUri: Uri,
|
||||||
|
contentType: String,
|
||||||
|
transferState: Int,
|
||||||
|
size: Long,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
fileName: String?,
|
||||||
|
fastPreflightId: String?,
|
||||||
|
voiceNote: Boolean,
|
||||||
|
borderless: Boolean,
|
||||||
|
videoGif: Boolean,
|
||||||
|
quote: Boolean,
|
||||||
|
caption: String?,
|
||||||
|
stickerLocator: StickerLocator?,
|
||||||
|
blurHash: BlurHash?,
|
||||||
|
audioHash: AudioHash?,
|
||||||
|
transformProperties: TransformProperties?
|
||||||
|
) : super(
|
||||||
|
contentType = contentType,
|
||||||
|
transferState = transferState,
|
||||||
|
size = size,
|
||||||
|
fileName = fileName,
|
||||||
|
cdnNumber = 0,
|
||||||
|
remoteLocation = null,
|
||||||
|
remoteKey = null,
|
||||||
|
remoteDigest = null,
|
||||||
|
incrementalDigest = null,
|
||||||
|
fastPreflightId = fastPreflightId,
|
||||||
|
voiceNote = voiceNote,
|
||||||
|
borderless = borderless,
|
||||||
|
videoGif = videoGif,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
incrementalMacChunkSize = 0,
|
||||||
|
quote = quote,
|
||||||
|
uploadTimestamp = 0,
|
||||||
|
caption = caption,
|
||||||
|
stickerLocator = stickerLocator,
|
||||||
|
blurHash = blurHash,
|
||||||
|
audioHash = audioHash,
|
||||||
|
transformProperties = transformProperties
|
||||||
|
) {
|
||||||
|
uri = Objects.requireNonNull(dataUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
|
uri = ParcelCompat.readParcelable(parcel, Uri::class.java.classLoader, Uri::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override val uri: Uri
|
||||||
|
override val publicUri: Uri? = null
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
super.writeToParcel(dest, flags)
|
||||||
|
dest.writeParcelable(uri, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other != null && other is UriAttachment && other.uri == uri
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uri.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import androidx.annotation.WorkerThread;
|
|||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
||||||
import org.thoughtcrime.securesms.media.MediaInput;
|
import org.thoughtcrime.securesms.video.interfaces.MediaInput;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import android.widget.TextView
|
|||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
import com.airbnb.lottie.SimpleColorFilter
|
import com.airbnb.lottie.SimpleColorFilter
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.avatar.Avatar
|
import org.thoughtcrime.securesms.avatar.Avatar
|
||||||
import org.thoughtcrime.securesms.avatar.AvatarRenderer
|
import org.thoughtcrime.securesms.avatar.AvatarRenderer
|
||||||
import org.thoughtcrime.securesms.avatar.Avatars
|
import org.thoughtcrime.securesms.avatar.Avatars
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||||
@@ -132,12 +132,12 @@ object AvatarPickerItem {
|
|||||||
}
|
}
|
||||||
is Avatar.Photo -> {
|
is Avatar.Photo -> {
|
||||||
textView.visible = false
|
textView.visible = false
|
||||||
GlideApp.with(imageView).load(DecryptableStreamUriLoader.DecryptableUri(model.avatar.uri)).into(imageView)
|
Glide.with(imageView).load(DecryptableStreamUriLoader.DecryptableUri(model.avatar.uri)).into(imageView)
|
||||||
}
|
}
|
||||||
is Avatar.Resource -> {
|
is Avatar.Resource -> {
|
||||||
imageView.setPadding((imageView.width * 0.2).toInt())
|
imageView.setPadding((imageView.width * 0.2).toInt())
|
||||||
textView.visible = false
|
textView.visible = false
|
||||||
GlideApp.with(imageView).clear(imageView)
|
Glide.with(imageView).clear(imageView)
|
||||||
imageView.setImageResource(model.avatar.resourceId)
|
imageView.setImageResource(model.avatar.resourceId)
|
||||||
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
|
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
|
||||||
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import android.util.AttributeSet
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.stories.Stories
|
import org.thoughtcrime.securesms.stories.Stories
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
@@ -76,7 +76,7 @@ class AvatarView @JvmOverloads constructor(
|
|||||||
/**
|
/**
|
||||||
* Displays Note-to-Self
|
* Displays Note-to-Self
|
||||||
*/
|
*/
|
||||||
fun displayChatAvatar(requestManager: GlideRequests, recipient: Recipient, isQuickContactEnabled: Boolean) {
|
fun displayChatAvatar(requestManager: RequestManager, recipient: Recipient, isQuickContactEnabled: Boolean) {
|
||||||
avatar.setAvatar(requestManager, recipient, isQuickContactEnabled)
|
avatar.setAvatar(requestManager, recipient, isQuickContactEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ object BackupCountQueries {
|
|||||||
@get:JvmStatic
|
@get:JvmStatic
|
||||||
val attachmentCount: String = """
|
val attachmentCount: String = """
|
||||||
SELECT COUNT(*) FROM ${AttachmentTable.TABLE_NAME}
|
SELECT COUNT(*) FROM ${AttachmentTable.TABLE_NAME}
|
||||||
INNER JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID}
|
INNER JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID}
|
||||||
WHERE ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 AND ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} <= 0
|
WHERE ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 AND ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} <= 0
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class BackupFrameOutputStream extends FullBackupBase.BackupStream {
|
|||||||
try {
|
try {
|
||||||
write(outputStream, new BackupFrame.Builder()
|
write(outputStream, new BackupFrame.Builder()
|
||||||
.attachment(new Attachment.Builder()
|
.attachment(new Attachment.Builder()
|
||||||
.rowId(attachmentId.getRowId())
|
.rowId(attachmentId.id)
|
||||||
.attachmentId(attachmentId.getUniqueId())
|
|
||||||
.length(Util.toIntExact(size))
|
.length(Util.toIntExact(size))
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.database.SessionTable;
|
|||||||
import org.thoughtcrime.securesms.database.SignedPreKeyTable;
|
import org.thoughtcrime.securesms.database.SignedPreKeyTable;
|
||||||
import org.thoughtcrime.securesms.database.StickerTable;
|
import org.thoughtcrime.securesms.database.StickerTable;
|
||||||
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
|
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
@@ -65,6 +64,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
@@ -77,6 +77,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
|
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
|
||||||
private static final long IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
|
private static final long IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
|
||||||
private static final long FINAL_MESSAGE_COUNT = 1L;
|
private static final long FINAL_MESSAGE_COUNT = 1L;
|
||||||
|
private static final long EXPIRATION_BACKUP_THRESHOLD = TimeUnit.DAYS.toMillis(1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tables in list will still have their *schema* exported (so the tables will be created),
|
* Tables in list will still have their *schema* exported (so the tables will be created),
|
||||||
@@ -159,15 +160,15 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
throwIfCanceled(cancellationSignal);
|
throwIfCanceled(cancellationSignal);
|
||||||
if (table.equals(MessageTable.TABLE_NAME)) {
|
if (table.equals(MessageTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isNonExpiringMessage(input, cursor), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(ReactionTable.TABLE_NAME)) {
|
} else if (table.equals(ReactionTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, new MessageId(CursorUtil.requireLong(cursor, ReactionTable.MESSAGE_ID))), null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, CursorUtil.requireLong(cursor, ReactionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(MentionTable.TABLE_NAME)) {
|
} else if (table.equals(MentionTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, CursorUtil.requireLong(cursor, MentionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, CursorUtil.requireLong(cursor, MentionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(GroupReceiptTable.TABLE_NAME)) {
|
} else if (table.equals(GroupReceiptTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptTable.MMS_ID))), null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptTable.MMS_ID))), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(AttachmentTable.TABLE_NAME)) {
|
} else if (table.equals(AttachmentTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.MESSAGE_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(StickerTable.TABLE_NAME)) {
|
} else if (table.equals(StickerTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
||||||
} else if (!TABLE_CONTENT_BLOCKLIST.contains(table)) {
|
} else if (!TABLE_CONTENT_BLOCKLIST.contains(table)) {
|
||||||
@@ -444,11 +445,10 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
long estimatedCount)
|
long estimatedCount)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.ROW_ID));
|
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.ID));
|
||||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.UNIQUE_ID));
|
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_SIZE));
|
||||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.SIZE));
|
|
||||||
|
|
||||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentTable.DATA));
|
String data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_FILE));
|
||||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_RANDOM));
|
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_RANDOM));
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(data)) {
|
if (!TextUtils.isEmpty(data)) {
|
||||||
@@ -457,14 +457,14 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
|
|
||||||
if (size <= 0 || fileLength != dbLength) {
|
if (size <= 0 || fileLength != dbLength) {
|
||||||
size = calculateVeryOldStreamLength(attachmentSecret, random, data);
|
size = calculateVeryOldStreamLength(attachmentSecret, random, data);
|
||||||
Log.w(TAG, "Needed size calculation! Manual: " + size + " File: " + fileLength + " DB: " + dbLength + " ID: " + new AttachmentId(rowId, uniqueId));
|
Log.w(TAG, "Needed size calculation! Manual: " + size + " File: " + fileLength + " DB: " + dbLength + " ID: " + new AttachmentId(rowId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
|
||||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||||
try (InputStream inputStream = openAttachmentStream(attachmentSecret, random, data)) {
|
try (InputStream inputStream = openAttachmentStream(attachmentSecret, random, data)) {
|
||||||
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
outputStream.write(new AttachmentId(rowId), inputStream, size);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.w(TAG, "Missing attachment", e);
|
Log.w(TAG, "Missing attachment", e);
|
||||||
}
|
}
|
||||||
@@ -579,27 +579,34 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
private static boolean isNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull Cursor cursor) {
|
||||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0 &&
|
long id = CursorUtil.requireLong(cursor, MessageTable.ID);
|
||||||
cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.VIEW_ONCE)) <= 0;
|
long expireStarted = CursorUtil.requireLong(cursor, MessageTable.EXPIRE_STARTED);
|
||||||
|
long expiresIn = CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN);
|
||||||
|
long latestRevisionId = CursorUtil.requireLong(cursor, MessageTable.LATEST_REVISION_ID);
|
||||||
|
|
||||||
|
long expiresAt = expireStarted + expiresIn;
|
||||||
|
long timeRemaining = expiresAt - System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (latestRevisionId > 0 && latestRevisionId != id ) {
|
||||||
|
return isForNonExpiringMessage(db, latestRevisionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expireStarted > 0 && timeRemaining <= EXPIRATION_BACKUP_THRESHOLD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNonExpiringSmsMessage(@NonNull Cursor cursor) {
|
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long messageId) {
|
||||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0;
|
String[] columns = new String[] { MessageTable.ID, MessageTable.EXPIRE_STARTED, MessageTable.EXPIRES_IN, MessageTable.LATEST_REVISION_ID };
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull MessageId messageId) {
|
|
||||||
return isForNonExpiringMmsMessage(db, messageId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForNonExpiringMmsMessage(@NonNull SQLiteDatabase db, long mmsId) {
|
|
||||||
String[] columns = new String[] { MessageTable.EXPIRES_IN, MessageTable.VIEW_ONCE };
|
|
||||||
String where = MessageTable.ID + " = ?";
|
String where = MessageTable.ID + " = ?";
|
||||||
String[] args = new String[] { String.valueOf(mmsId) };
|
String[] args = SqlUtil.buildArgs(messageId);
|
||||||
|
|
||||||
try (Cursor mmsCursor = db.query(MessageTable.TABLE_NAME, columns, where, args, null, null, null)) {
|
try (Cursor mmsCursor = db.query(MessageTable.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||||
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
||||||
return isNonExpiringMmsMessage(mmsCursor);
|
return isNonExpiringMessage(db, mmsCursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,26 +194,33 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
File dataFile = AttachmentTable.newFile(context);
|
File dataFile = AttachmentTable.newFile(context);
|
||||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||||
|
boolean isLegacyTable = SqlUtil.tableExists(db, "part");
|
||||||
|
|
||||||
|
String dataFileColumnName = isLegacyTable ? "_data" : AttachmentTable.DATA_FILE;
|
||||||
|
String dataRandomColumnName = isLegacyTable ? "data_random" : AttachmentTable.DATA_RANDOM;
|
||||||
|
String idColumnName = isLegacyTable ? "_id" : AttachmentTable.ID;
|
||||||
|
String tableName = isLegacyTable ? "part" : AttachmentTable.TABLE_NAME;
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
inputStream.readAttachmentTo(output.second, attachment.length);
|
inputStream.readAttachmentTo(output.second, attachment.length);
|
||||||
|
|
||||||
contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath());
|
contentValues.put(dataFileColumnName, dataFile.getAbsolutePath());
|
||||||
contentValues.put(AttachmentTable.DATA_RANDOM, output.first);
|
contentValues.put(dataRandomColumnName, output.first);
|
||||||
} catch (BackupRecordInputStream.BadMacException e) {
|
} catch (BackupRecordInputStream.BadMacException e) {
|
||||||
Log.w(TAG, "Bad MAC for attachment " + attachment.attachmentId + "! Can't restore it.", e);
|
Log.w(TAG, "Bad MAC for attachment " + attachment.attachmentId + "! Can't restore it.", e);
|
||||||
dataFile.delete();
|
dataFile.delete();
|
||||||
contentValues.put(AttachmentTable.DATA, (String) null);
|
contentValues.put(dataFileColumnName, (String) null);
|
||||||
contentValues.put(AttachmentTable.DATA_RANDOM, (String) null);
|
contentValues.put(dataRandomColumnName, (String) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.update(AttachmentTable.TABLE_NAME, contentValues,
|
db.update(tableName,
|
||||||
AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?",
|
contentValues,
|
||||||
new String[] {String.valueOf(attachment.rowId), String.valueOf(attachment.attachmentId)});
|
idColumnName + " = ?",
|
||||||
|
SqlUtil.buildArgs(attachment.rowId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream)
|
private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.whispersystems.signalservice.api.NetworkResult
|
import org.whispersystems.signalservice.api.NetworkResult
|
||||||
import org.whispersystems.signalservice.api.archive.ArchiveGetBackupInfoResponse
|
|
||||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
@@ -168,7 +167,7 @@ object BackupRepository {
|
|||||||
/**
|
/**
|
||||||
* Returns an object with details about the remote backup state.
|
* Returns an object with details about the remote backup state.
|
||||||
*/
|
*/
|
||||||
fun getRemoteBackupState(): NetworkResult<ArchiveGetBackupInfoResponse> {
|
fun getRemoteBackupState(): NetworkResult<BackupMetadata> {
|
||||||
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
@@ -182,6 +181,18 @@ object BackupRepository {
|
|||||||
}
|
}
|
||||||
.then { credential ->
|
.then { credential ->
|
||||||
api.getBackupInfo(backupKey, credential)
|
api.getBackupInfo(backupKey, credential)
|
||||||
|
.map { it to credential }
|
||||||
|
}
|
||||||
|
.then { pair ->
|
||||||
|
val (info, credential) = pair
|
||||||
|
api.debugGetUploadedMediaItemMetadata(backupKey, credential)
|
||||||
|
.also { Log.i(TAG, "MediaItemMetadataResult: $it") }
|
||||||
|
.map { mediaObjects ->
|
||||||
|
BackupMetadata(
|
||||||
|
usedSpace = info.usedSpace ?: 0,
|
||||||
|
mediaCount = mediaObjects.size.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,3 +266,8 @@ class BackupState {
|
|||||||
val chatIdToBackupRecipientId = HashMap<Long, Long>()
|
val chatIdToBackupRecipientId = HashMap<Long, Long>()
|
||||||
val callIdToType = HashMap<Long, Long>()
|
val callIdToType = HashMap<Long, Long>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BackupMetadata(
|
||||||
|
val usedSpace: Long,
|
||||||
|
val mediaCount: Long
|
||||||
|
)
|
||||||
|
|||||||
+2
-2
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
package org.thoughtcrime.securesms.backup.v2.database
|
package org.thoughtcrime.securesms.backup.v2.database
|
||||||
|
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||||
|
|
||||||
fun AttachmentTable.clearAllDataForBackupRestore() {
|
fun AttachmentTable.clearAllDataForBackupRestore() {
|
||||||
writableDatabase.delete(AttachmentTable.TABLE_NAME).run()
|
writableDatabase.deleteAll(AttachmentTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -109,7 +109,8 @@ class CallLogIterator(private val cursor: Cursor) : Iterator<BackupCall?>, Close
|
|||||||
CallTable.Event.DECLINED -> Call.Event.DECLINED
|
CallTable.Event.DECLINED -> Call.Event.DECLINED
|
||||||
CallTable.Event.GENERIC_GROUP_CALL -> Call.Event.GENERIC_GROUP_CALL
|
CallTable.Event.GENERIC_GROUP_CALL -> Call.Event.GENERIC_GROUP_CALL
|
||||||
CallTable.Event.JOINED -> Call.Event.JOINED
|
CallTable.Event.JOINED -> Call.Event.JOINED
|
||||||
CallTable.Event.MISSED -> Call.Event.MISSED
|
CallTable.Event.MISSED,
|
||||||
|
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> Call.Event.MISSED
|
||||||
CallTable.Event.DELETE -> Call.Event.DELETE
|
CallTable.Event.DELETE -> Call.Event.DELETE
|
||||||
CallTable.Event.RINGING -> Call.Event.UNKNOWN_EVENT
|
CallTable.Event.RINGING -> Call.Event.UNKNOWN_EVENT
|
||||||
CallTable.Event.NOT_ACCEPTED -> Call.Event.NOT_ACCEPTED
|
CallTable.Event.NOT_ACCEPTED -> Call.Event.NOT_ACCEPTED
|
||||||
|
|||||||
+3
-5
@@ -7,7 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.database
|
|||||||
|
|
||||||
import okio.ByteString.Companion.toByteString
|
import okio.ByteString.Companion.toByteString
|
||||||
import org.signal.core.util.CursorUtil
|
import org.signal.core.util.CursorUtil
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.readToList
|
import org.signal.core.util.readToList
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
@@ -88,12 +88,10 @@ fun DistributionListTables.restoreFromBackup(dlist: BackupDistributionList, back
|
|||||||
|
|
||||||
fun DistributionListTables.clearAllDataForBackupRestore() {
|
fun DistributionListTables.clearAllDataForBackupRestore() {
|
||||||
writableDatabase
|
writableDatabase
|
||||||
.delete(DistributionListTables.ListTable.TABLE_NAME)
|
.deleteAll(DistributionListTables.ListTable.TABLE_NAME)
|
||||||
.run()
|
|
||||||
|
|
||||||
writableDatabase
|
writableDatabase
|
||||||
.delete(DistributionListTables.MembershipTable.TABLE_NAME)
|
.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME)
|
||||||
.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DistributionListPrivacyMode.toBackupPrivacyMode(): BackupDistributionList.PrivacyMode {
|
private fun DistributionListPrivacyMode.toBackupPrivacyMode(): BackupDistributionList.PrivacyMode {
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ import android.database.Cursor
|
|||||||
import okio.ByteString.Companion.toByteString
|
import okio.ByteString.Companion.toByteString
|
||||||
import org.signal.core.util.Base64
|
import org.signal.core.util.Base64
|
||||||
import org.signal.core.util.SqlUtil
|
import org.signal.core.util.SqlUtil
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.nullIfBlank
|
import org.signal.core.util.nullIfBlank
|
||||||
import org.signal.core.util.requireBoolean
|
import org.signal.core.util.requireBoolean
|
||||||
@@ -155,7 +155,7 @@ fun RecipientTable.restoreSelfFromBackup(accountData: AccountData, selfId: Recip
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun RecipientTable.clearAllDataForBackupRestore() {
|
fun RecipientTable.clearAllDataForBackupRestore() {
|
||||||
writableDatabase.delete(RecipientTable.TABLE_NAME).run()
|
writableDatabase.deleteAll(RecipientTable.TABLE_NAME)
|
||||||
SqlUtil.resetAutoIncrementValue(writableDatabase, RecipientTable.TABLE_NAME)
|
SqlUtil.resetAutoIncrementValue(writableDatabase, RecipientTable.TABLE_NAME)
|
||||||
|
|
||||||
RecipientId.clearCache()
|
RecipientId.clearCache()
|
||||||
|
|||||||
+2
-2
@@ -56,7 +56,7 @@ object AccountDataProcessor {
|
|||||||
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context),
|
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context),
|
||||||
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
|
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
|
||||||
linkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
|
linkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
|
||||||
notDiscoverableByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isUnlisted,
|
notDiscoverableByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode.isUndiscoverable,
|
||||||
phoneNumberSharingMode = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
|
phoneNumberSharingMode = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
|
||||||
preferContactAvatars = SignalStore.settings().isPreferSystemContactPhotos,
|
preferContactAvatars = SignalStore.settings().isPreferSystemContactPhotos,
|
||||||
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
||||||
@@ -86,7 +86,7 @@ object AccountDataProcessor {
|
|||||||
TextSecurePreferences.setTypingIndicatorsEnabled(context, settings.typingIndicators)
|
TextSecurePreferences.setTypingIndicatorsEnabled(context, settings.typingIndicators)
|
||||||
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, settings.sealedSenderIndicators)
|
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, settings.sealedSenderIndicators)
|
||||||
SignalStore.settings().isLinkPreviewsEnabled = settings.linkPreviews
|
SignalStore.settings().isLinkPreviewsEnabled = settings.linkPreviews
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = if (settings.notDiscoverableByPhoneNumber) PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED else PhoneNumberPrivacyValues.PhoneNumberListingMode.LISTED
|
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = if (settings.notDiscoverableByPhoneNumber) PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.DISCOVERABLE
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = settings.phoneNumberSharingMode.toLocalPhoneNumberMode()
|
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = settings.phoneNumberSharingMode.toLocalPhoneNumberMode()
|
||||||
SignalStore.settings().isPreferSystemContactPhotos = settings.preferContactAvatars
|
SignalStore.settings().isPreferSystemContactPhotos = settings.preferContactAvatars
|
||||||
SignalStore.settings().universalExpireTimer = settings.universalExpireTimer
|
SignalStore.settings().universalExpireTimer = settings.universalExpireTimer
|
||||||
|
|||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import org.signal.core.ui.BottomSheets
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.signal.core.util.money.FiatMoney
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.subscription.models.GooglePayButton
|
||||||
|
import org.thoughtcrime.securesms.databinding.PaypalButtonBinding
|
||||||
|
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.Currency
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsCheckoutSheet(
|
||||||
|
messageBackupsType: MessageBackupsType,
|
||||||
|
availablePaymentGateways: List<GatewayResponse.Gateway>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onPaymentGatewaySelected: (GatewayResponse.Gateway) -> Unit
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
dragHandle = { BottomSheets.Handle() },
|
||||||
|
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
) {
|
||||||
|
SheetContent(
|
||||||
|
messageBackupsType = messageBackupsType,
|
||||||
|
availablePaymentGateways = availablePaymentGateways,
|
||||||
|
onPaymentGatewaySelected = onPaymentGatewaySelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SheetContent(
|
||||||
|
messageBackupsType: MessageBackupsType,
|
||||||
|
availablePaymentGateways: List<GatewayResponse.Gateway>,
|
||||||
|
onPaymentGatewaySelected: (GatewayResponse.Gateway) -> Unit
|
||||||
|
) {
|
||||||
|
val resources = LocalContext.current.resources
|
||||||
|
val formattedPrice = remember(messageBackupsType.pricePerMonth) {
|
||||||
|
FiatMoneyUtil.format(resources, messageBackupsType.pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Pay $formattedPrice/month to Signal", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(top = 48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "You'll get:", // TODO [message-backups] Finalized copy
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 5.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
MessageBackupsTypeBlock(
|
||||||
|
messageBackupsType = messageBackupsType,
|
||||||
|
isSelected = false,
|
||||||
|
onSelected = {},
|
||||||
|
enabled = false,
|
||||||
|
modifier = Modifier.padding(top = 24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = spacedBy(12.dp),
|
||||||
|
modifier = Modifier.padding(top = 48.dp, bottom = 24.dp)
|
||||||
|
) {
|
||||||
|
availablePaymentGateways.forEach {
|
||||||
|
when (it) {
|
||||||
|
GatewayResponse.Gateway.GOOGLE_PAY -> GooglePayButton {
|
||||||
|
onPaymentGatewaySelected(GatewayResponse.Gateway.GOOGLE_PAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayResponse.Gateway.PAYPAL -> PayPalButton {
|
||||||
|
onPaymentGatewaySelected(GatewayResponse.Gateway.PAYPAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayResponse.Gateway.CREDIT_CARD -> CreditOrDebitCardButton {
|
||||||
|
onPaymentGatewaySelected(GatewayResponse.Gateway.CREDIT_CARD)
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayResponse.Gateway.SEPA_DEBIT -> SepaButton {
|
||||||
|
onPaymentGatewaySelected(GatewayResponse.Gateway.SEPA_DEBIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayResponse.Gateway.IDEAL -> IdealButton {
|
||||||
|
onPaymentGatewaySelected(GatewayResponse.Gateway.IDEAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PayPalButton(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
AndroidView(factory = {
|
||||||
|
val view = LayoutInflater.from(it).inflate(R.layout.paypal_button, null)
|
||||||
|
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
view
|
||||||
|
}) {
|
||||||
|
val binding = PaypalButtonBinding.bind(it)
|
||||||
|
binding.paypalButton.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
marginStart = 0
|
||||||
|
marginEnd = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.paypalButton.setOnClickListener {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GooglePayButton(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val model = GooglePayButton.Model(onClick, true)
|
||||||
|
|
||||||
|
AndroidView(factory = {
|
||||||
|
LayoutInflater.from(it).inflate(R.layout.google_pay_button_pref, null)
|
||||||
|
}) {
|
||||||
|
val holder = GooglePayButton.ViewHolder(it)
|
||||||
|
holder.bind(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SepaButton(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Buttons.LargeTonal(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.bank_transfer),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(id = R.string.GatewaySelectorBottomSheet__bank_transfer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun IdealButton(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Buttons.LargeTonal(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.logo_ideal),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(id = R.string.GatewaySelectorBottomSheet__ideal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CreditOrDebitCardButton(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Buttons.LargePrimary(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.credit_card),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.GatewaySelectorBottomSheet__credit_or_debit_card)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsCheckoutSheetPreview() {
|
||||||
|
val paidTier = MessageBackupsType(
|
||||||
|
pricePerMonth = FiatMoney(BigDecimal.valueOf(3), Currency.getInstance("USD")),
|
||||||
|
title = "Text + All your media",
|
||||||
|
features = persistentListOf(
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||||
|
label = "Full text message backup"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||||
|
label = "Full media backup"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||||
|
label = "1TB of storage (~250K photos)"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_heart_compact_bold_16,
|
||||||
|
label = "Thanks for supporting Signal!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val availablePaymentGateways = GatewayResponse.Gateway.values().toList()
|
||||||
|
|
||||||
|
Previews.Preview {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
) {
|
||||||
|
SheetContent(
|
||||||
|
messageBackupsType = paidTier,
|
||||||
|
availablePaymentGateways = availablePaymentGateways,
|
||||||
|
onPaymentGatewaySelected = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.signal.core.ui.Scaffolds
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Educational content which allows user to proceed to set up automatic backups
|
||||||
|
* or navigate to a support page to learn more.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsEducationScreen(
|
||||||
|
onNavigationClick: () -> Unit,
|
||||||
|
onEnableBackups: () -> Unit,
|
||||||
|
onLearnMore: () -> Unit
|
||||||
|
) {
|
||||||
|
Scaffolds.Settings(
|
||||||
|
onNavigationClick = onNavigationClick,
|
||||||
|
navigationIconPainter = painterResource(id = R.drawable.symbol_x_24),
|
||||||
|
title = "Chat backups" // TODO [message-backups] Finalized copy
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(it)
|
||||||
|
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_signal_logo_large), // TODO [message-backups] Final image asset
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 48.dp)
|
||||||
|
.size(88.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Chat Backups", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(top = 15.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Back up your messages and media and using Signal’s secure, end-to-end encrypted storage service. Never lose a message when you get a new phone or reinstall Signal.", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(top = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(top = 32.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(20.dp)
|
||||||
|
) {
|
||||||
|
NotableFeatureRow(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_lock_compact_20),
|
||||||
|
text = "End-to-end Encrypted" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
|
||||||
|
NotableFeatureRow(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_check_square_compact_20),
|
||||||
|
text = "Optional, always" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
|
||||||
|
NotableFeatureRow(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_trash_compact_20),
|
||||||
|
text = "Delete your backup anytime" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buttons.LargePrimary(
|
||||||
|
onClick = onEnableBackups,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Enable backups" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onLearnMore,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Learn more" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsEducationSheetPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
MessageBackupsEducationScreen(
|
||||||
|
onNavigationClick = {},
|
||||||
|
onEnableBackups = {},
|
||||||
|
onLearnMore = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun NotableFeatureRowPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
NotableFeatureRow(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_lock_compact_20),
|
||||||
|
text = "Notable feature information"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NotableFeatureRow(
|
||||||
|
painter: Painter,
|
||||||
|
text: String
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(32.dp)
|
||||||
|
.background(color = SignalTheme.colors.colorSurface2, shape = CircleShape)
|
||||||
|
.padding(6.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.dialog
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||||
|
import org.thoughtcrime.securesms.util.viewModel
|
||||||
|
|
||||||
|
class MessageBackupsFlowActivity : PassphraseRequiredActivity() {
|
||||||
|
|
||||||
|
private val viewModel: MessageBackupsFlowViewModel by viewModel { MessageBackupsFlowViewModel() }
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||||
|
setContent {
|
||||||
|
SignalTheme {
|
||||||
|
val state by viewModel.state
|
||||||
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
fun MessageBackupsScreen.next() {
|
||||||
|
val nextScreen = viewModel.goToNextScreen(this)
|
||||||
|
if (nextScreen != this) {
|
||||||
|
navController.navigate(nextScreen.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavController.popOrFinish() {
|
||||||
|
if (popBackStack()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
finishAfterTransition()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
navController.setLifecycleOwner(this@MessageBackupsFlowActivity)
|
||||||
|
navController.setOnBackPressedDispatcher(this@MessageBackupsFlowActivity.onBackPressedDispatcher)
|
||||||
|
navController.enableOnBackPressed(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = MessageBackupsScreen.EDUCATION.name,
|
||||||
|
enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
|
||||||
|
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
|
||||||
|
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
|
||||||
|
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) }
|
||||||
|
) {
|
||||||
|
composable(route = MessageBackupsScreen.EDUCATION.name) {
|
||||||
|
MessageBackupsEducationScreen(
|
||||||
|
onNavigationClick = navController::popOrFinish,
|
||||||
|
onEnableBackups = { MessageBackupsScreen.EDUCATION.next() },
|
||||||
|
onLearnMore = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(route = MessageBackupsScreen.PIN_EDUCATION.name) {
|
||||||
|
MessageBackupsPinEducationScreen(
|
||||||
|
onNavigationClick = navController::popOrFinish,
|
||||||
|
onGeneratePinClick = {},
|
||||||
|
onUseCurrentPinClick = { MessageBackupsScreen.PIN_EDUCATION.next() },
|
||||||
|
recommendedPinSize = 16 // TODO [message-backups] This value should come from some kind of config
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(route = MessageBackupsScreen.PIN_CONFIRMATION.name) {
|
||||||
|
MessageBackupsPinConfirmationScreen(
|
||||||
|
pin = state.pin,
|
||||||
|
onPinChanged = viewModel::onPinEntryUpdated,
|
||||||
|
pinKeyboardType = state.pinKeyboardType,
|
||||||
|
onPinKeyboardTypeSelected = viewModel::onPinKeyboardTypeUpdated,
|
||||||
|
onNextClick = { MessageBackupsScreen.PIN_CONFIRMATION.next() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(route = MessageBackupsScreen.TYPE_SELECTION.name) {
|
||||||
|
MessageBackupsTypeSelectionScreen(
|
||||||
|
selectedBackupsType = state.selectedMessageBackupsType,
|
||||||
|
availableBackupsTypes = state.availableBackupsTypes,
|
||||||
|
onMessageBackupsTypeSelected = viewModel::onMessageBackupsTypeUpdated,
|
||||||
|
onNavigationClick = navController::popOrFinish,
|
||||||
|
onReadMoreClicked = {},
|
||||||
|
onNextClicked = { MessageBackupsScreen.TYPE_SELECTION.next() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog(route = MessageBackupsScreen.CHECKOUT_SHEET.name) {
|
||||||
|
MessageBackupsCheckoutSheet(
|
||||||
|
messageBackupsType = state.selectedMessageBackupsType!!,
|
||||||
|
availablePaymentGateways = state.availablePaymentGateways,
|
||||||
|
onDismissRequest = navController::popOrFinish,
|
||||||
|
onPaymentGatewaySelected = {
|
||||||
|
viewModel.onPaymentGatewayUpdated(it)
|
||||||
|
MessageBackupsScreen.CHECKOUT_SHEET.next()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
class MessageBackupsFlowRepository
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||||
|
|
||||||
|
data class MessageBackupsFlowState(
|
||||||
|
val selectedMessageBackupsType: MessageBackupsType? = null,
|
||||||
|
val availableBackupsTypes: List<MessageBackupsType> = emptyList(),
|
||||||
|
val selectedPaymentGateway: GatewayResponse.Gateway? = null,
|
||||||
|
val availablePaymentGateways: List<GatewayResponse.Gateway> = emptyList(),
|
||||||
|
val pin: String = "",
|
||||||
|
val pinKeyboardType: PinKeyboardType = SignalStore.pinValues().keyboardType
|
||||||
|
)
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||||
|
|
||||||
|
class MessageBackupsFlowViewModel : ViewModel() {
|
||||||
|
private val internalState = mutableStateOf(MessageBackupsFlowState())
|
||||||
|
|
||||||
|
val state: State<MessageBackupsFlowState> = internalState
|
||||||
|
|
||||||
|
fun goToNextScreen(currentScreen: MessageBackupsScreen): MessageBackupsScreen {
|
||||||
|
return when (currentScreen) {
|
||||||
|
MessageBackupsScreen.EDUCATION -> MessageBackupsScreen.PIN_EDUCATION
|
||||||
|
MessageBackupsScreen.PIN_EDUCATION -> MessageBackupsScreen.PIN_CONFIRMATION
|
||||||
|
MessageBackupsScreen.PIN_CONFIRMATION -> validatePinAndUpdateState()
|
||||||
|
MessageBackupsScreen.TYPE_SELECTION -> validateTypeAndUpdateState()
|
||||||
|
MessageBackupsScreen.CHECKOUT_SHEET -> validateGatewayAndUpdateState()
|
||||||
|
MessageBackupsScreen.PROCESS_PAYMENT -> MessageBackupsScreen.COMPLETED
|
||||||
|
MessageBackupsScreen.COMPLETED -> error("Unsupported state transition from terminal state COMPLETED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPinEntryUpdated(pin: String) {
|
||||||
|
internalState.value = state.value.copy(pin = pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPinKeyboardTypeUpdated(pinKeyboardType: PinKeyboardType) {
|
||||||
|
internalState.value = state.value.copy(pinKeyboardType = pinKeyboardType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPaymentGatewayUpdated(gateway: GatewayResponse.Gateway) {
|
||||||
|
internalState.value = state.value.copy(selectedPaymentGateway = gateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageBackupsTypeUpdated(messageBackupsType: MessageBackupsType) {
|
||||||
|
internalState.value = state.value.copy(selectedMessageBackupsType = messageBackupsType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validatePinAndUpdateState(): MessageBackupsScreen {
|
||||||
|
return MessageBackupsScreen.TYPE_SELECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateTypeAndUpdateState(): MessageBackupsScreen {
|
||||||
|
return MessageBackupsScreen.CHECKOUT_SHEET
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateGatewayAndUpdateState(): MessageBackupsScreen {
|
||||||
|
return MessageBackupsScreen.PROCESS_PAYMENT
|
||||||
|
}
|
||||||
|
}
|
||||||
+198
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen which requires the user to enter their pin before enabling backups.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsPinConfirmationScreen(
|
||||||
|
pin: String,
|
||||||
|
onPinChanged: (String) -> Unit,
|
||||||
|
pinKeyboardType: PinKeyboardType,
|
||||||
|
onPinKeyboardTypeSelected: (PinKeyboardType) -> Unit,
|
||||||
|
onNextClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Enter your PIN", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(top = 40.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Enter your Signal PIN to enable backups", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
// TODO [message-backups] Confirm default focus state
|
||||||
|
val keyboardType = remember(pinKeyboardType) {
|
||||||
|
when (pinKeyboardType) {
|
||||||
|
PinKeyboardType.NUMERIC -> KeyboardType.NumberPassword
|
||||||
|
PinKeyboardType.ALPHA_NUMERIC -> KeyboardType.Password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = pin,
|
||||||
|
onValueChange = onPinChanged,
|
||||||
|
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = { onNextClick() }
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = keyboardType
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 72.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 48.dp)
|
||||||
|
) {
|
||||||
|
PinKeyboardTypeToggle(
|
||||||
|
pinKeyboardType = pinKeyboardType,
|
||||||
|
onPinKeyboardTypeSelected = onPinKeyboardTypeSelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.BottomEnd,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
Buttons.LargeTonal(
|
||||||
|
onClick = onNextClick
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Next" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsPinConfirmationScreenPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
MessageBackupsPinConfirmationScreen(
|
||||||
|
pin = "",
|
||||||
|
onPinChanged = {},
|
||||||
|
pinKeyboardType = PinKeyboardType.ALPHA_NUMERIC,
|
||||||
|
onPinKeyboardTypeSelected = {},
|
||||||
|
onNextClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PinKeyboardTypeTogglePreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
var type by remember { mutableStateOf(PinKeyboardType.ALPHA_NUMERIC) }
|
||||||
|
PinKeyboardTypeToggle(
|
||||||
|
pinKeyboardType = type,
|
||||||
|
onPinKeyboardTypeSelected = { type = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PinKeyboardTypeToggle(
|
||||||
|
pinKeyboardType: PinKeyboardType,
|
||||||
|
onPinKeyboardTypeSelected: (PinKeyboardType) -> Unit
|
||||||
|
) {
|
||||||
|
val callback = remember(pinKeyboardType) {
|
||||||
|
{ onPinKeyboardTypeSelected(pinKeyboardType.other) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconRes = remember(pinKeyboardType) {
|
||||||
|
when (pinKeyboardType) {
|
||||||
|
PinKeyboardType.NUMERIC -> R.drawable.symbol_keyboard_24
|
||||||
|
PinKeyboardType.ALPHA_NUMERIC -> R.drawable.symbol_number_pad_24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(onClick = callback) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = iconRes),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Switch keyboard" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.signal.core.ui.Scaffolds
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explanation screen that details how the user's pin is utilized with backups,
|
||||||
|
* and how long they should make their pin.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsPinEducationScreen(
|
||||||
|
onNavigationClick: () -> Unit,
|
||||||
|
onGeneratePinClick: () -> Unit,
|
||||||
|
onUseCurrentPinClick: () -> Unit,
|
||||||
|
recommendedPinSize: Int
|
||||||
|
) {
|
||||||
|
Scaffolds.Settings(
|
||||||
|
title = "Backup type", // TODO [message-backups] Finalized copy
|
||||||
|
onNavigationClick = onNavigationClick,
|
||||||
|
navigationIconPainter = painterResource(id = R.drawable.symbol_arrow_left_24)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(it)
|
||||||
|
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_signal_logo_large), // TODO [message-backups] Finalized image
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 48.dp)
|
||||||
|
.size(88.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "PINs protect your backup", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Your Signal PIN lets you restore your backup when you re-install Signal. For increased security, we recommend updating to a new $recommendedPinSize-digit PIN.", // TODO [message-backups] Finalized copy
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "If you forget your PIN, you will not be able to restore your backup. You can change your PIN at any time in settings.", // TODO [message-backups] Finalized copy
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buttons.LargePrimary(
|
||||||
|
onClick = onGeneratePinClick,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Generate a new $recommendedPinSize-digit PIN" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onUseCurrentPinClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Use current Signal PIN" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsPinScreenPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
MessageBackupsPinEducationScreen(
|
||||||
|
onNavigationClick = {},
|
||||||
|
onGeneratePinClick = {},
|
||||||
|
onUseCurrentPinClick = {},
|
||||||
|
recommendedPinSize = 16
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
enum class MessageBackupsScreen {
|
||||||
|
EDUCATION,
|
||||||
|
PIN_EDUCATION,
|
||||||
|
PIN_CONFIRMATION,
|
||||||
|
TYPE_SELECTION,
|
||||||
|
CHECKOUT_SHEET,
|
||||||
|
PROCESS_PAYMENT,
|
||||||
|
COMPLETED
|
||||||
|
}
|
||||||
+302
@@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms.backup.v2.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.ExperimentalTextApi
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.withAnnotation
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.signal.core.ui.Scaffolds
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
import org.signal.core.util.money.FiatMoney
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.Currency
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen which allows the user to select their preferred backup type.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalTextApi::class)
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsTypeSelectionScreen(
|
||||||
|
selectedBackupsType: MessageBackupsType?,
|
||||||
|
availableBackupsTypes: List<MessageBackupsType>,
|
||||||
|
onMessageBackupsTypeSelected: (MessageBackupsType) -> Unit,
|
||||||
|
onNavigationClick: () -> Unit,
|
||||||
|
onReadMoreClicked: () -> Unit,
|
||||||
|
onNextClicked: () -> Unit
|
||||||
|
) {
|
||||||
|
Scaffolds.Settings(
|
||||||
|
title = "",
|
||||||
|
onNavigationClick = onNavigationClick,
|
||||||
|
navigationIconPainter = painterResource(id = R.drawable.symbol_arrow_left_24)
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_signal_logo_large), // TODO [message-backups] Finalized art asset
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(88.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Choose your backup type", // TODO [message-backups] Finalized copy
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(top = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
// TODO [message-backups] Finalized copy
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
val readMoreString = buildAnnotatedString {
|
||||||
|
append("All backups are end-to-end encrypted. Signal is a non-profit—paying for backups helps support our mission. ")
|
||||||
|
withAnnotation(tag = "URL", annotation = "read-more") {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = primaryColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Read more")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = readMoreString,
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Center),
|
||||||
|
onClick = { offset ->
|
||||||
|
readMoreString
|
||||||
|
.getStringAnnotations(tag = "URL", start = offset, end = offset)
|
||||||
|
.firstOrNull()?.let { onReadMoreClicked() }
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(
|
||||||
|
availableBackupsTypes,
|
||||||
|
{ _, item -> item.title }
|
||||||
|
) { index, item ->
|
||||||
|
MessageBackupsTypeBlock(
|
||||||
|
messageBackupsType = item,
|
||||||
|
isSelected = item == selectedBackupsType,
|
||||||
|
onSelected = { onMessageBackupsTypeSelected(item) },
|
||||||
|
modifier = Modifier.padding(top = if (index == 0) 20.dp else 18.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buttons.LargePrimary(
|
||||||
|
onClick = onNextClicked,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Next" // TODO [message-backups] Finalized copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsTypeSelectionScreenPreview() {
|
||||||
|
val freeTier = MessageBackupsType(
|
||||||
|
pricePerMonth = FiatMoney(BigDecimal.ZERO, Currency.getInstance("USD")),
|
||||||
|
title = "Text + 30 days of media",
|
||||||
|
features = persistentListOf(
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||||
|
label = "Full text message backup"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||||
|
label = "Last 30 days of media"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val paidTier = MessageBackupsType(
|
||||||
|
pricePerMonth = FiatMoney(BigDecimal.valueOf(3), Currency.getInstance("USD")),
|
||||||
|
title = "Text + All your media",
|
||||||
|
features = persistentListOf(
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||||
|
label = "Full text message backup"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||||
|
label = "Full media backup"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||||
|
label = "1TB of storage (~250K photos)"
|
||||||
|
),
|
||||||
|
MessageBackupsTypeFeature(
|
||||||
|
iconResourceId = R.drawable.symbol_heart_compact_bold_16,
|
||||||
|
label = "Thanks for supporting Signal!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var selectedBackupsType by remember { mutableStateOf(freeTier) }
|
||||||
|
|
||||||
|
Previews.Preview {
|
||||||
|
MessageBackupsTypeSelectionScreen(
|
||||||
|
selectedBackupsType = selectedBackupsType,
|
||||||
|
availableBackupsTypes = listOf(freeTier, paidTier),
|
||||||
|
onMessageBackupsTypeSelected = { selectedBackupsType = it },
|
||||||
|
onNavigationClick = {},
|
||||||
|
onReadMoreClicked = {},
|
||||||
|
onNextClicked = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageBackupsTypeBlock(
|
||||||
|
messageBackupsType: MessageBackupsType,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onSelected: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true
|
||||||
|
) {
|
||||||
|
val borderColor = if (isSelected) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
val background = if (isSelected) {
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
} else {
|
||||||
|
SignalTheme.colors.colorSurface2
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(color = background, shape = RoundedCornerShape(18.dp))
|
||||||
|
.border(width = 2.dp, color = borderColor, shape = RoundedCornerShape(18.dp))
|
||||||
|
.clip(shape = RoundedCornerShape(18.dp))
|
||||||
|
.clickable(onClick = onSelected, enabled = enabled)
|
||||||
|
.padding(vertical = 16.dp, horizontal = 20.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = formatCostPerMonth(messageBackupsType.pricePerMonth),
|
||||||
|
style = MaterialTheme.typography.titleSmall
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = messageBackupsType.title,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = spacedBy(4.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
messageBackupsType.features.forEach {
|
||||||
|
MessageBackupsTypeFeatureRow(messageBackupsTypeFeature = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun formatCostPerMonth(pricePerMonth: FiatMoney): String {
|
||||||
|
return if (pricePerMonth.amount == BigDecimal.ZERO) {
|
||||||
|
"Free"
|
||||||
|
} else {
|
||||||
|
"${FiatMoneyUtil.format(LocalContext.current.resources, pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())}/month"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MessageBackupsTypeFeatureRow(messageBackupsTypeFeature: MessageBackupsTypeFeature) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = messageBackupsTypeFeature.iconResourceId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = messageBackupsTypeFeature.label,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MessageBackupsType(
|
||||||
|
val pricePerMonth: FiatMoney,
|
||||||
|
val title: String,
|
||||||
|
val features: ImmutableList<MessageBackupsTypeFeature>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MessageBackupsTypeFeature(
|
||||||
|
val iconResourceId: Int,
|
||||||
|
val label: String
|
||||||
|
)
|
||||||
@@ -4,6 +4,8 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
||||||
@@ -11,8 +13,6 @@ import org.thoughtcrime.securesms.badges.models.Badge
|
|||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSize
|
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSize
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||||
import org.thoughtcrime.securesms.glide.GiftBadgeModel
|
import org.thoughtcrime.securesms.glide.GiftBadgeModel
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.ScreenDensity
|
import org.thoughtcrime.securesms.util.ScreenDensity
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||||
@@ -43,35 +43,35 @@ class BadgeImageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setBadgeFromRecipient(recipient: Recipient?) {
|
fun setBadgeFromRecipient(recipient: Recipient?) {
|
||||||
getGlideRequests()?.let {
|
getGlideRequestManager()?.let {
|
||||||
setBadgeFromRecipient(recipient, it)
|
setBadgeFromRecipient(recipient, it)
|
||||||
} ?: clearDrawable()
|
} ?: clearDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBadgeFromRecipient(recipient: Recipient?, glideRequests: GlideRequests) {
|
fun setBadgeFromRecipient(recipient: Recipient?, requestManager: RequestManager) {
|
||||||
if (recipient == null || recipient.badges.isEmpty()) {
|
if (recipient == null || recipient.badges.isEmpty()) {
|
||||||
setBadge(null, glideRequests)
|
setBadge(null, requestManager)
|
||||||
} else if (recipient.isSelf) {
|
} else if (recipient.isSelf) {
|
||||||
val badge = recipient.featuredBadge
|
val badge = recipient.featuredBadge
|
||||||
if (badge == null || !badge.visible || badge.isExpired()) {
|
if (badge == null || !badge.visible || badge.isExpired()) {
|
||||||
setBadge(null, glideRequests)
|
setBadge(null, requestManager)
|
||||||
} else {
|
} else {
|
||||||
setBadge(badge, glideRequests)
|
setBadge(badge, requestManager)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setBadge(recipient.featuredBadge, glideRequests)
|
setBadge(recipient.featuredBadge, requestManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBadge(badge: Badge?) {
|
fun setBadge(badge: Badge?) {
|
||||||
getGlideRequests()?.let {
|
getGlideRequestManager()?.let {
|
||||||
setBadge(badge, it)
|
setBadge(badge, it)
|
||||||
} ?: clearDrawable()
|
} ?: clearDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBadge(badge: Badge?, glideRequests: GlideRequests) {
|
fun setBadge(badge: Badge?, requestManager: RequestManager) {
|
||||||
if (badge != null) {
|
if (badge != null) {
|
||||||
glideRequests
|
requestManager
|
||||||
.load(badge)
|
.load(badge)
|
||||||
.downsample(DownsampleStrategy.NONE)
|
.downsample(DownsampleStrategy.NONE)
|
||||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||||
@@ -79,21 +79,21 @@ class BadgeImageView @JvmOverloads constructor(
|
|||||||
|
|
||||||
isClickable = true
|
isClickable = true
|
||||||
} else {
|
} else {
|
||||||
glideRequests
|
requestManager
|
||||||
.clear(this)
|
.clear(this)
|
||||||
clearDrawable()
|
clearDrawable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGiftBadge(badge: GiftBadge?, glideRequests: GlideRequests) {
|
fun setGiftBadge(badge: GiftBadge?, requestManager: RequestManager) {
|
||||||
if (badge != null) {
|
if (badge != null) {
|
||||||
glideRequests
|
requestManager
|
||||||
.load(GiftBadgeModel(badge))
|
.load(GiftBadgeModel(badge))
|
||||||
.downsample(DownsampleStrategy.NONE)
|
.downsample(DownsampleStrategy.NONE)
|
||||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), ScreenDensity.getBestDensityBucketForDevice(), ThemeUtil.isDarkTheme(context)))
|
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), ScreenDensity.getBestDensityBucketForDevice(), ThemeUtil.isDarkTheme(context)))
|
||||||
.into(this)
|
.into(this)
|
||||||
} else {
|
} else {
|
||||||
glideRequests
|
requestManager
|
||||||
.clear(this)
|
.clear(this)
|
||||||
clearDrawable()
|
clearDrawable()
|
||||||
}
|
}
|
||||||
@@ -106,9 +106,9 @@ class BadgeImageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGlideRequests(): GlideRequests? {
|
private fun getGlideRequestManager(): RequestManager? {
|
||||||
return try {
|
return try {
|
||||||
GlideApp.with(this)
|
Glide.with(this)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// View not attached to an activity or activity destroyed
|
// View not attached to an activity or activity destroyed
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import android.widget.TextView
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.signal.core.util.DimensionUnit
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
import org.thoughtcrime.securesms.badges.gifts.Gifts.formatExpiry
|
import org.thoughtcrime.securesms.badges.gifts.Gifts.formatExpiry
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +50,7 @@ class GiftMessageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGiftBadge(glideRequests: GlideRequests, giftBadge: GiftBadge, isOutgoing: Boolean, callback: Callback, fromRecipient: Recipient, toRecipient: Recipient) {
|
fun setGiftBadge(requestManager: RequestManager, giftBadge: GiftBadge, isOutgoing: Boolean, callback: Callback, fromRecipient: Recipient, toRecipient: Recipient) {
|
||||||
descriptionView.text = giftBadge.formatExpiry(context)
|
descriptionView.text = giftBadge.formatExpiry(context)
|
||||||
actionView.icon = null
|
actionView.icon = null
|
||||||
actionView.setOnClickListener { callback.onViewGiftBadgeClicked() }
|
actionView.setOnClickListener { callback.onViewGiftBadgeClicked() }
|
||||||
@@ -88,7 +88,7 @@ class GiftMessageView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
badgeView.setGiftBadge(giftBadge, glideRequests)
|
badgeView.setGiftBadge(giftBadge, requestManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onGiftNotOpened() {
|
fun onGiftNotOpened() {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.view.View
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.Key
|
import com.bumptech.glide.load.Key
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||||
@@ -14,7 +15,6 @@ import kotlinx.parcelize.Parcelize
|
|||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
@@ -130,7 +130,7 @@ data class Badge(
|
|||||||
|
|
||||||
badge.alpha = if (model.badge.isExpired() || model.isFaded) 0.5f else 1f
|
badge.alpha = if (model.badge.isExpired() || model.isFaded) 0.5f else 1f
|
||||||
|
|
||||||
GlideApp.with(badge)
|
Glide.with(badge)
|
||||||
.load(model.badge)
|
.load(model.badge)
|
||||||
.downsample(DownsampleStrategy.NONE)
|
.downsample(DownsampleStrategy.NONE)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package org.thoughtcrime.securesms.badges.models
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||||
@@ -49,7 +49,7 @@ object BadgeDisplay112 {
|
|||||||
|
|
||||||
override fun bind(model: GiftModel) {
|
override fun bind(model: GiftModel) {
|
||||||
titleView.visible = false
|
titleView.visible = false
|
||||||
badgeImageView.setGiftBadge(model.giftBadge, GlideApp.with(badgeImageView))
|
badgeImageView.setGiftBadge(model.giftBadge, Glide.with(badgeImageView))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ object CallLinks {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.startsWith(HTTPS_LINK_PREFIX) || !url.startsWith(SNGL_LINK_PREFIX)) {
|
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import androidx.annotation.DrawableRes
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.database.CallTable
|
import org.thoughtcrime.securesms.database.CallTable
|
||||||
import org.thoughtcrime.securesms.database.MessageTypes
|
import org.thoughtcrime.securesms.database.MessageTypes
|
||||||
import org.thoughtcrime.securesms.databinding.CallLogAdapterItemBinding
|
import org.thoughtcrime.securesms.databinding.CallLogAdapterItemBinding
|
||||||
import org.thoughtcrime.securesms.databinding.CallLogCreateCallLinkItemBinding
|
import org.thoughtcrime.securesms.databinding.CallLogCreateCallLinkItemBinding
|
||||||
import org.thoughtcrime.securesms.databinding.ConversationListItemClearFilterBinding
|
import org.thoughtcrime.securesms.databinding.ConversationListItemClearFilterBinding
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.SearchUtil
|
import org.thoughtcrime.securesms.util.SearchUtil
|
||||||
@@ -272,7 +272,7 @@ class CallLogAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun presentRecipientDetails(recipient: Recipient, searchQuery: String?) {
|
private fun presentRecipientDetails(recipient: Recipient, searchQuery: String?) {
|
||||||
binding.callRecipientAvatar.setAvatar(GlideApp.with(binding.callRecipientAvatar), recipient, true)
|
binding.callRecipientAvatar.setAvatar(Glide.with(binding.callRecipientAvatar), recipient, true)
|
||||||
binding.callRecipientBadge.setBadgeFromRecipient(recipient)
|
binding.callRecipientBadge.setBadgeFromRecipient(recipient)
|
||||||
binding.callRecipientName.text = if (searchQuery != null) {
|
binding.callRecipientName.text = if (searchQuery != null) {
|
||||||
SearchUtil.getHighlightedSpan(
|
SearchUtil.getHighlightedSpan(
|
||||||
@@ -305,7 +305,7 @@ class CallLogAdapter(
|
|||||||
|
|
||||||
val color = ContextCompat.getColor(
|
val color = ContextCompat.getColor(
|
||||||
context,
|
context,
|
||||||
if (call.record.event == CallTable.Event.MISSED) {
|
if (call.record.event.isMissedCall()) {
|
||||||
R.color.signal_colorError
|
R.color.signal_colorError
|
||||||
} else {
|
} else {
|
||||||
R.color.signal_colorOnSurfaceVariant
|
R.color.signal_colorOnSurfaceVariant
|
||||||
@@ -375,7 +375,7 @@ class CallLogAdapter(
|
|||||||
MessageTypes.OUTGOING_AUDIO_CALL_TYPE, MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_upright_compact_16
|
MessageTypes.OUTGOING_AUDIO_CALL_TYPE, MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_upright_compact_16
|
||||||
MessageTypes.GROUP_CALL_TYPE -> when {
|
MessageTypes.GROUP_CALL_TYPE -> when {
|
||||||
call.type == CallTable.Type.AD_HOC_CALL -> R.drawable.symbol_link_compact_16
|
call.type == CallTable.Type.AD_HOC_CALL -> R.drawable.symbol_link_compact_16
|
||||||
call.event == CallTable.Event.MISSED -> R.drawable.symbol_missed_incoming_compact_16
|
call.event.isMissedCall() -> R.drawable.symbol_missed_incoming_compact_16
|
||||||
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.drawable.symbol_group_compact_16
|
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.drawable.symbol_group_compact_16
|
||||||
call.direction == CallTable.Direction.INCOMING -> R.drawable.symbol_arrow_downleft_compact_16
|
call.direction == CallTable.Direction.INCOMING -> R.drawable.symbol_arrow_downleft_compact_16
|
||||||
call.direction == CallTable.Direction.OUTGOING -> R.drawable.symbol_arrow_upright_compact_16
|
call.direction == CallTable.Direction.OUTGOING -> R.drawable.symbol_arrow_upright_compact_16
|
||||||
@@ -389,8 +389,8 @@ class CallLogAdapter(
|
|||||||
@StringRes
|
@StringRes
|
||||||
private fun getCallStateStringRes(call: CallTable.Call): Int {
|
private fun getCallStateStringRes(call: CallTable.Call): Int {
|
||||||
return when (call.messageType) {
|
return when (call.messageType) {
|
||||||
MessageTypes.MISSED_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__missed
|
MessageTypes.MISSED_VIDEO_CALL_TYPE,
|
||||||
MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__missed
|
MessageTypes.MISSED_AUDIO_CALL_TYPE -> if (call.event == CallTable.Event.MISSED) R.string.CallLogAdapter__missed else R.string.CallLogAdapter__missed_notification_profile
|
||||||
MessageTypes.INCOMING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__incoming
|
MessageTypes.INCOMING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__incoming
|
||||||
MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__incoming
|
MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__incoming
|
||||||
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__outgoing
|
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__outgoing
|
||||||
@@ -398,6 +398,7 @@ class CallLogAdapter(
|
|||||||
MessageTypes.GROUP_CALL_TYPE -> when {
|
MessageTypes.GROUP_CALL_TYPE -> when {
|
||||||
call.type == CallTable.Type.AD_HOC_CALL -> R.string.CallLogAdapter__call_link
|
call.type == CallTable.Type.AD_HOC_CALL -> R.string.CallLogAdapter__call_link
|
||||||
call.event == CallTable.Event.MISSED -> R.string.CallLogAdapter__missed
|
call.event == CallTable.Event.MISSED -> R.string.CallLogAdapter__missed
|
||||||
|
call.event == CallTable.Event.MISSED_NOTIFICATION_PROFILE -> R.string.CallLogAdapter__missed_notification_profile
|
||||||
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.string.CallPreference__group_call
|
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.string.CallPreference__group_call
|
||||||
call.direction == CallTable.Direction.INCOMING -> R.string.CallLogAdapter__incoming
|
call.direction == CallTable.Direction.INCOMING -> R.string.CallLogAdapter__incoming
|
||||||
call.direction == CallTable.Direction.OUTGOING -> R.string.CallLogAdapter__outgoing
|
call.direction == CallTable.Direction.OUTGOING -> R.string.CallLogAdapter__outgoing
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import androidx.annotation.IdRes;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
@@ -30,9 +31,9 @@ import java.util.List;
|
|||||||
public class AlbumThumbnailView extends FrameLayout {
|
public class AlbumThumbnailView extends FrameLayout {
|
||||||
|
|
||||||
private @Nullable SlideClickListener thumbnailClickListener;
|
private @Nullable SlideClickListener thumbnailClickListener;
|
||||||
private @Nullable SlidesClickedListener downloadClickListener;
|
private @Nullable SlidesClickedListener startTransferClickListener;
|
||||||
private @Nullable SlidesClickedListener cancelDownloadClickListener;
|
private @Nullable SlidesClickedListener cancelTransferClickListener;
|
||||||
private @Nullable SlideClickListener playVideoClickListener;
|
private @Nullable SlideClickListener playVideoClickListener;
|
||||||
|
|
||||||
private int currentSizeClass;
|
private int currentSizeClass;
|
||||||
|
|
||||||
@@ -65,23 +66,23 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
transferControlsStub = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
|
transferControlsStub = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides, boolean showControls) {
|
public void setSlides(@NonNull RequestManager requestManager, @NonNull List<Slide> slides, boolean showControls) {
|
||||||
if (slides.size() < 2) {
|
if (slides.size() < 2) {
|
||||||
throw new IllegalStateException("Provided less than two slides.");
|
throw new IllegalStateException("Provided less than two slides.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showControls) {
|
if (showControls) {
|
||||||
transferControlsStub.get().setShowSecondaryText(true);
|
transferControlsStub.get().setShowSecondaryText(true);
|
||||||
transferControlsStub.get().setDownloadClickListener(
|
transferControlsStub.get().setTransferClickListener(
|
||||||
v -> {
|
v -> {
|
||||||
if (downloadClickListener != null) {
|
if (startTransferClickListener != null) {
|
||||||
downloadClickListener.onClick(v, slides);
|
startTransferClickListener.onClick(v, slides);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
transferControlsStub.get().setCancelClickListener(
|
transferControlsStub.get().setCancelClickListener(
|
||||||
v -> {
|
v -> {
|
||||||
if (cancelDownloadClickListener != null) {
|
if (cancelTransferClickListener != null) {
|
||||||
cancelDownloadClickListener.onClick(v, slides);
|
cancelTransferClickListener.onClick(v, slides);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
transferControlsStub.get().setSlides(slides);
|
transferControlsStub.get().setSlides(slides);
|
||||||
@@ -98,7 +99,7 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
currentSizeClass = sizeClass;
|
currentSizeClass = sizeClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
showSlides(glideRequests, slides);
|
showSlides(requestManager, slides);
|
||||||
applyCorners();
|
applyCorners();
|
||||||
forceLayout();
|
forceLayout();
|
||||||
}
|
}
|
||||||
@@ -117,12 +118,12 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
thumbnailClickListener = listener;
|
thumbnailClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDownloadClickListener(SlidesClickedListener listener) {
|
public void setStartTransferClickListener(SlidesClickedListener listener) {
|
||||||
this.downloadClickListener = listener;
|
this.startTransferClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCancelDownloadClickListener(SlidesClickedListener listener) {
|
public void setCancelTransferClickListener(SlidesClickedListener listener) {
|
||||||
this.cancelDownloadClickListener = listener;
|
this.cancelTransferClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayVideoClickListener(SlideClickListener listener) {
|
public void setPlayVideoClickListener(SlideClickListener listener) {
|
||||||
@@ -261,21 +262,21 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
applyCornersForSizeClass5();
|
applyCornersForSizeClass5();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides) {
|
private void showSlides(@NonNull RequestManager requestManager, @NonNull List<Slide> slides) {
|
||||||
boolean showControls = TransferControlView.containsPlayableSlides(slides);
|
boolean showControls = TransferControlView.containsPlayableSlides(slides);
|
||||||
setSlide(glideRequests, slides.get(0), R.id.album_cell_1, showControls);
|
setSlide(requestManager, slides.get(0), R.id.album_cell_1, showControls);
|
||||||
setSlide(glideRequests, slides.get(1), R.id.album_cell_2, showControls);
|
setSlide(requestManager, slides.get(1), R.id.album_cell_2, showControls);
|
||||||
|
|
||||||
if (slides.size() >= 3) {
|
if (slides.size() >= 3) {
|
||||||
setSlide(glideRequests, slides.get(2), R.id.album_cell_3, showControls);
|
setSlide(requestManager, slides.get(2), R.id.album_cell_3, showControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slides.size() >= 4) {
|
if (slides.size() >= 4) {
|
||||||
setSlide(glideRequests, slides.get(3), R.id.album_cell_4, showControls);
|
setSlide(requestManager, slides.get(3), R.id.album_cell_4, showControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slides.size() >= 5) {
|
if (slides.size() >= 5) {
|
||||||
setSlide(glideRequests, slides.get(4), R.id.album_cell_5, showControls && slides.size() == 5);
|
setSlide(requestManager, slides.get(4), R.id.album_cell_5, showControls && slides.size() == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slides.size() > 5) {
|
if (slides.size() > 5) {
|
||||||
@@ -284,17 +285,17 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id, boolean showControls) {
|
private void setSlide(@NonNull RequestManager requestManager, @NonNull Slide slide, @IdRes int id, boolean showControls) {
|
||||||
ThumbnailView cell = findViewById(id);
|
ThumbnailView cell = findViewById(id);
|
||||||
cell.showSecondaryText(false);
|
cell.showSecondaryText(false);
|
||||||
cell.setThumbnailClickListener(defaultThumbnailClickListener);
|
cell.setThumbnailClickListener(defaultThumbnailClickListener);
|
||||||
cell.setDownloadClickListener(downloadClickListener);
|
cell.setStartTransferClickListener(startTransferClickListener);
|
||||||
cell.setCancelDownloadClickListener(cancelDownloadClickListener);
|
cell.setCancelTransferClickListener(cancelTransferClickListener);
|
||||||
if (MediaUtil.isInstantVideoSupported(slide)) {
|
if (MediaUtil.isInstantVideoSupported(slide)) {
|
||||||
cell.setPlayVideoClickListener(playVideoClickListener);
|
cell.setPlayVideoClickListener(playVideoClickListener);
|
||||||
}
|
}
|
||||||
cell.setOnLongClickListener(defaultLongClickListener);
|
cell.setOnLongClickListener(defaultLongClickListener);
|
||||||
cell.setImageResource(glideRequests, slide, showControls, false);
|
cell.setImageResource(requestManager, slide, showControls, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int sizeClass(int size) {
|
private int sizeClass(int size) {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import androidx.annotation.Px;
|
|||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.DataSource;
|
import com.bumptech.glide.load.DataSource;
|
||||||
import com.bumptech.glide.load.MultiTransformation;
|
import com.bumptech.glide.load.MultiTransformation;
|
||||||
import com.bumptech.glide.load.Transformation;
|
import com.bumptech.glide.load.Transformation;
|
||||||
@@ -37,9 +40,6 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
|||||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
@@ -129,10 +129,10 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
*/
|
*/
|
||||||
public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) {
|
public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) {
|
||||||
if (recipient.isSelf()) {
|
if (recipient.isSelf()) {
|
||||||
setAvatar(GlideApp.with(this), null, quickContactEnabled);
|
setAvatar(Glide.with(this), null, quickContactEnabled);
|
||||||
AvatarUtil.loadIconIntoImageView(recipient, this);
|
AvatarUtil.loadIconIntoImageView(recipient, this);
|
||||||
} else {
|
} else {
|
||||||
setAvatar(GlideApp.with(this), recipient, quickContactEnabled);
|
setAvatar(Glide.with(this), recipient, quickContactEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,21 +144,21 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
* Shows self as the note to self icon.
|
* Shows self as the note to self icon.
|
||||||
*/
|
*/
|
||||||
public void setAvatar(@Nullable Recipient recipient) {
|
public void setAvatar(@Nullable Recipient recipient) {
|
||||||
setAvatar(GlideApp.with(this), recipient, false);
|
setAvatar(Glide.with(this), recipient, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows self as the profile avatar.
|
* Shows self as the profile avatar.
|
||||||
*/
|
*/
|
||||||
public void setAvatarUsingProfile(@Nullable Recipient recipient) {
|
public void setAvatarUsingProfile(@Nullable Recipient recipient) {
|
||||||
setAvatar(GlideApp.with(this), recipient, false, true);
|
setAvatar(Glide.with(this), recipient, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
||||||
setAvatar(requestManager, recipient, quickContactEnabled, false);
|
setAvatar(requestManager, recipient, quickContactEnabled, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled, boolean useSelfProfileAvatar) {
|
public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled, boolean useSelfProfileAvatar) {
|
||||||
setAvatar(requestManager, recipient, new AvatarOptions.Builder(this)
|
setAvatar(requestManager, recipient, new AvatarOptions.Builder(this)
|
||||||
.withUseSelfProfileAvatar(useSelfProfileAvatar)
|
.withUseSelfProfileAvatar(useSelfProfileAvatar)
|
||||||
.withQuickContactEnabled(quickContactEnabled)
|
.withQuickContactEnabled(quickContactEnabled)
|
||||||
@@ -166,10 +166,10 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setAvatar(@Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
private void setAvatar(@Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
||||||
setAvatar(GlideApp.with(this), recipient, avatarOptions);
|
setAvatar(Glide.with(this), recipient, avatarOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
private void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
RecipientContactPhoto photo = (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) ? new RecipientContactPhoto(recipient,
|
RecipientContactPhoto photo = (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) ? new RecipientContactPhoto(recipient,
|
||||||
new ProfileContactPhoto(Recipient.self()))
|
new ProfileContactPhoto(Recipient.self()))
|
||||||
@@ -199,7 +199,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
transforms.add(new CircleCrop());
|
transforms.add(new CircleCrop());
|
||||||
blurred = shouldBlur;
|
blurred = shouldBlur;
|
||||||
|
|
||||||
GlideRequest<Drawable> request = requestManager.load(photo.contactPhoto)
|
RequestBuilder<Drawable> request = requestManager.load(photo.contactPhoto)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.fallback(fallbackContactPhotoDrawable)
|
.fallback(fallbackContactPhotoDrawable)
|
||||||
.error(fallbackContactPhotoDrawable)
|
.error(fallbackContactPhotoDrawable)
|
||||||
@@ -265,7 +265,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
.getPhotoForGroup()
|
.getPhotoForGroup()
|
||||||
.asDrawable(getContext(), color);
|
.asDrawable(getContext(), color);
|
||||||
|
|
||||||
GlideApp.with(this)
|
Glide.with(this)
|
||||||
.load(avatarBytes)
|
.load(avatarBytes)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ import android.widget.ImageView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterInside;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
@@ -55,15 +53,15 @@ public class BorderlessImageView extends FrameLayout {
|
|||||||
image.setOnLongClickListener(l);
|
image.setOnLongClickListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
public void setSlide(@NonNull RequestManager requestManager, @NonNull Slide slide) {
|
||||||
boolean showControls = slide.asAttachment().getUri() == null;
|
boolean showControls = slide.asAttachment().getUri() == null;
|
||||||
|
|
||||||
if (slide.hasSticker()) {
|
if (slide.hasSticker()) {
|
||||||
image.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
image.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
image.setImageResource(glideRequests, slide, showControls, false);
|
image.setImageResource(requestManager, slide, showControls, false);
|
||||||
} else {
|
} else {
|
||||||
image.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
image.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
|
image.setImageResource(requestManager, slide, showControls, false, slide.asAttachment().width, slide.asAttachment().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE);
|
missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE);
|
||||||
@@ -74,6 +72,6 @@ public class BorderlessImageView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDownloadClickListener(@NonNull SlidesClickedListener listener) {
|
public void setDownloadClickListener(@NonNull SlidesClickedListener listener) {
|
||||||
image.setDownloadClickListener(listener);
|
image.setStartTransferClickListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,23 +114,23 @@ public final class ContactFilterView extends FrameLayout {
|
|||||||
int defStyle)
|
int defStyle)
|
||||||
{
|
{
|
||||||
final TypedArray attributes = context.obtainStyledAttributes(attrs,
|
final TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||||
R.styleable.ContactFilterToolbar,
|
R.styleable.ContactFilterView,
|
||||||
defStyle,
|
defStyle,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
int styleResource = attributes.getResourceId(R.styleable.ContactFilterToolbar_searchTextStyle, -1);
|
int styleResource = attributes.getResourceId(R.styleable.ContactFilterView_searchTextStyle, -1);
|
||||||
if (styleResource != -1) {
|
if (styleResource != -1) {
|
||||||
TextViewCompat.setTextAppearance(searchText, styleResource);
|
TextViewCompat.setTextAppearance(searchText, styleResource);
|
||||||
}
|
}
|
||||||
if (!attributes.getBoolean(R.styleable.ContactFilterToolbar_showDialpad, true)) {
|
if (!attributes.getBoolean(R.styleable.ContactFilterView_showDialpad, true)) {
|
||||||
dialpadToggle.setVisibility(GONE);
|
dialpadToggle.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.getBoolean(R.styleable.ContactFilterToolbar_cfv_autoFocus, true)) {
|
if (attributes.getBoolean(R.styleable.ContactFilterView_cfv_autoFocus, true)) {
|
||||||
searchText.requestFocus();
|
searchText.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
int backgroundRes = attributes.getResourceId(R.styleable.ContactFilterToolbar_cfv_background, -1);
|
int backgroundRes = attributes.getResourceId(R.styleable.ContactFilterView_cfv_background, -1);
|
||||||
if (backgroundRes != -1) {
|
if (backgroundRes != -1) {
|
||||||
findViewById(R.id.background_holder).setBackgroundResource(backgroundRes);
|
findViewById(R.id.background_holder).setBackgroundResource(backgroundRes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
|||||||
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode;
|
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode;
|
||||||
import org.thoughtcrime.securesms.conversation.v2.computed.FormattedDate;
|
import org.thoughtcrime.securesms.conversation.v2.computed.FormattedDate;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@@ -302,7 +302,9 @@ public class ConversationItemFooter extends ConstraintLayout {
|
|||||||
|
|
||||||
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale, @NonNull ConversationItemDisplayMode displayMode) {
|
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale, @NonNull ConversationItemDisplayMode displayMode) {
|
||||||
dateView.forceLayout();
|
dateView.forceLayout();
|
||||||
if (messageRecord.isFailed()) {
|
if (messageRecord.isMediaPending()) {
|
||||||
|
dateView.setText(null);
|
||||||
|
} else if (messageRecord.isFailed()) {
|
||||||
int errorMsg;
|
int errorMsg;
|
||||||
if (messageRecord.hasFailedWithNetworkFailures()) {
|
if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||||
errorMsg = R.string.ConversationItem_error_network_not_delivered;
|
errorMsg = R.string.ConversationItem_error_network_not_delivered;
|
||||||
|
|||||||
+10
-10
@@ -16,10 +16,10 @@ import androidx.annotation.ColorInt
|
|||||||
import androidx.annotation.Px
|
import androidx.annotation.Px
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import org.signal.core.util.dp
|
import org.signal.core.util.dp
|
||||||
import org.signal.core.util.getParcelableCompat
|
import org.signal.core.util.getParcelableCompat
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide
|
import org.thoughtcrime.securesms.mms.Slide
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener
|
import org.thoughtcrime.securesms.mms.SlideClickListener
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener
|
||||||
@@ -192,7 +192,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun setImageResource(
|
fun setImageResource(
|
||||||
glideRequests: GlideRequests,
|
requestManager: RequestManager,
|
||||||
slides: List<Slide>,
|
slides: List<Slide>,
|
||||||
showControls: Boolean,
|
showControls: Boolean,
|
||||||
isPreview: Boolean
|
isPreview: Boolean
|
||||||
@@ -223,7 +223,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
|
|
||||||
val attachment = slides[0].asAttachment()
|
val attachment = slides[0].asAttachment()
|
||||||
|
|
||||||
thumbnail.get().setImageResource(glideRequests, slides[0], showControls, isPreview, attachment.width, attachment.height)
|
thumbnail.get().setImageResource(requestManager, slides[0], showControls, isPreview, attachment.width, attachment.height)
|
||||||
touchDelegate = thumbnail.get().touchDelegate
|
touchDelegate = thumbnail.get().touchDelegate
|
||||||
} else {
|
} else {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
@@ -232,7 +232,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
album.get().setSlides(glideRequests, slides, showControls)
|
album.get().setSlides(requestManager, slides, showControls)
|
||||||
touchDelegate = album.get().touchDelegate
|
touchDelegate = album.get().touchDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,10 +251,10 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDownloadClickListener(listener: SlidesClickedListener?) {
|
fun setStartTransferClickListener(listener: SlidesClickedListener?) {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
thumbnailViewState = state.thumbnailViewState.copy(downloadClickListener = listener),
|
thumbnailViewState = state.thumbnailViewState.copy(startTransferClickListener = listener),
|
||||||
albumViewState = state.albumViewState.copy(downloadClickListener = listener)
|
albumViewState = state.albumViewState.copy(startTransferClickListener = listener)
|
||||||
)
|
)
|
||||||
|
|
||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
@@ -269,10 +269,10 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCancelDownloadClickListener(listener: SlidesClickedListener?) {
|
fun setCancelTransferClickListener(listener: SlidesClickedListener?) {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
thumbnailViewState = state.thumbnailViewState.copy(cancelDownloadClickListener = listener),
|
thumbnailViewState = state.thumbnailViewState.copy(cancelTransferClickListener = listener),
|
||||||
albumViewState = state.albumViewState.copy(cancelDownloadClickListener = listener)
|
albumViewState = state.albumViewState.copy(cancelTransferClickListener = listener)
|
||||||
)
|
)
|
||||||
|
|
||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
|
|||||||
+8
-8
@@ -34,9 +34,9 @@ data class ConversationItemThumbnailState(
|
|||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val clickListener: SlideClickListener? = null,
|
private val clickListener: SlideClickListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val downloadClickListener: SlidesClickedListener? = null,
|
private val startTransferClickListener: SlidesClickedListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val cancelDownloadClickListener: SlidesClickedListener? = null,
|
private val cancelTransferClickListener: SlidesClickedListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val playVideoClickListener: SlideClickListener? = null,
|
private val playVideoClickListener: SlideClickListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
@@ -63,8 +63,8 @@ data class ConversationItemThumbnailState(
|
|||||||
thumbnailView.get().isClickable = clickable
|
thumbnailView.get().isClickable = clickable
|
||||||
thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||||
thumbnailView.get().setThumbnailClickListener(clickListener)
|
thumbnailView.get().setThumbnailClickListener(clickListener)
|
||||||
thumbnailView.get().setDownloadClickListener(downloadClickListener)
|
thumbnailView.get().setStartTransferClickListener(startTransferClickListener)
|
||||||
thumbnailView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
|
thumbnailView.get().setCancelTransferClickListener(cancelTransferClickListener)
|
||||||
thumbnailView.get().setPlayVideoClickListener(playVideoClickListener)
|
thumbnailView.get().setPlayVideoClickListener(playVideoClickListener)
|
||||||
thumbnailView.get().setOnLongClickListener(longClickListener)
|
thumbnailView.get().setOnLongClickListener(longClickListener)
|
||||||
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
|
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
|
||||||
@@ -78,9 +78,9 @@ data class ConversationItemThumbnailState(
|
|||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val clickListener: SlideClickListener? = null,
|
private val clickListener: SlideClickListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val downloadClickListener: SlidesClickedListener? = null,
|
private val startTransferClickListener: SlidesClickedListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val cancelDownloadClickListener: SlidesClickedListener? = null,
|
private val cancelTransferClickListener: SlidesClickedListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val playVideoClickListener: SlideClickListener? = null,
|
private val playVideoClickListener: SlideClickListener? = null,
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
@@ -103,8 +103,8 @@ data class ConversationItemThumbnailState(
|
|||||||
albumView.get().isClickable = clickable
|
albumView.get().isClickable = clickable
|
||||||
albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||||
albumView.get().setThumbnailClickListener(clickListener)
|
albumView.get().setThumbnailClickListener(clickListener)
|
||||||
albumView.get().setDownloadClickListener(downloadClickListener)
|
albumView.get().setStartTransferClickListener(startTransferClickListener)
|
||||||
albumView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
|
albumView.get().setCancelTransferClickListener(cancelTransferClickListener)
|
||||||
albumView.get().setPlayVideoClickListener(playVideoClickListener)
|
albumView.get().setPlayVideoClickListener(playVideoClickListener)
|
||||||
albumView.get().setOnLongClickListener(longClickListener)
|
albumView.get().setOnLongClickListener(longClickListener)
|
||||||
albumView.get().setCellBackgroundColor(cellBackgroundColor)
|
albumView.get().setCellBackgroundColor(cellBackgroundColor)
|
||||||
|
|||||||
+11
-10
@@ -11,9 +11,10 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -49,7 +50,7 @@ public class ConversationTypingView extends ConstraintLayout {
|
|||||||
indicator = findViewById(R.id.typing_indicator);
|
indicator = findViewById(R.id.typing_indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTypists(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists, boolean isGroupThread, boolean hasWallpaper) {
|
public void setTypists(@NonNull RequestManager requestManager, @NonNull List<Recipient> typists, boolean isGroupThread, boolean hasWallpaper) {
|
||||||
if (typists.isEmpty()) {
|
if (typists.isEmpty()) {
|
||||||
indicator.stopAnimation();
|
indicator.stopAnimation();
|
||||||
return;
|
return;
|
||||||
@@ -64,7 +65,7 @@ public class ConversationTypingView extends ConstraintLayout {
|
|||||||
typistCount.setVisibility(GONE);
|
typistCount.setVisibility(GONE);
|
||||||
|
|
||||||
if (isGroupThread) {
|
if (isGroupThread) {
|
||||||
presentGroupThreadAvatars(glideRequests, typists);
|
presentGroupThreadAvatars(requestManager, typists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasWallpaper) {
|
if (hasWallpaper) {
|
||||||
@@ -84,23 +85,23 @@ public class ConversationTypingView extends ConstraintLayout {
|
|||||||
return indicator.isActive();
|
return indicator.isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentGroupThreadAvatars(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists) {
|
private void presentGroupThreadAvatars(@NonNull RequestManager requestManager, @NonNull List<Recipient> typists) {
|
||||||
avatar1.setAvatar(glideRequests, typists.get(0), typists.size() == 1);
|
avatar1.setAvatar(requestManager, typists.get(0), typists.size() == 1);
|
||||||
avatar1.setVisibility(VISIBLE);
|
avatar1.setVisibility(VISIBLE);
|
||||||
badge1.setBadgeFromRecipient(typists.get(0), glideRequests);
|
badge1.setBadgeFromRecipient(typists.get(0), requestManager);
|
||||||
badge1.setVisibility(VISIBLE);
|
badge1.setVisibility(VISIBLE);
|
||||||
|
|
||||||
if (typists.size() > 1) {
|
if (typists.size() > 1) {
|
||||||
avatar2.setAvatar(glideRequests, typists.get(1), false);
|
avatar2.setAvatar(requestManager, typists.get(1), false);
|
||||||
avatar2.setVisibility(VISIBLE);
|
avatar2.setVisibility(VISIBLE);
|
||||||
badge2.setBadgeFromRecipient(typists.get(1), glideRequests);
|
badge2.setBadgeFromRecipient(typists.get(1), requestManager);
|
||||||
badge2.setVisibility(VISIBLE);
|
badge2.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typists.size() == 3) {
|
if (typists.size() == 3) {
|
||||||
avatar3.setAvatar(glideRequests, typists.get(2), false);
|
avatar3.setAvatar(requestManager, typists.get(2), false);
|
||||||
avatar3.setVisibility(VISIBLE);
|
avatar3.setVisibility(VISIBLE);
|
||||||
badge3.setBadgeFromRecipient(typists.get(2), glideRequests);
|
badge3.setBadgeFromRecipient(typists.get(2), requestManager);
|
||||||
badge3.setVisibility(VISIBLE);
|
badge3.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
@@ -146,7 +147,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
|||||||
setVisibility(View.VISIBLE);
|
setVisibility(View.VISIBLE);
|
||||||
ViewUtil.setPaddingStart(this, 0);
|
ViewUtil.setPaddingStart(this, 0);
|
||||||
ViewUtil.setPaddingEnd(this, horizontalPadding);
|
ViewUtil.setPaddingEnd(this, horizontalPadding);
|
||||||
setImageResource(R.drawable.ic_delivery_status_sending);
|
setImageResource(R.drawable.symbol_messagestatus_sending_24);
|
||||||
updateContentDescription();
|
updateContentDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
|||||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||||
ViewUtil.setPaddingEnd(this, 0);
|
ViewUtil.setPaddingEnd(this, 0);
|
||||||
clearAnimation();
|
clearAnimation();
|
||||||
setImageResource(R.drawable.ic_delivery_status_sent);
|
setImageResource(R.drawable.symbol_messagestatus_sent_24);
|
||||||
updateContentDescription();
|
updateContentDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +167,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
|||||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||||
ViewUtil.setPaddingEnd(this, 0);
|
ViewUtil.setPaddingEnd(this, 0);
|
||||||
clearAnimation();
|
clearAnimation();
|
||||||
setImageResource(R.drawable.ic_delivery_status_delivered);
|
setImageResource(R.drawable.symbol_messagestatus_delivered_24);
|
||||||
updateContentDescription();
|
updateContentDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,12 +177,12 @@ public class DeliveryStatusView extends AppCompatImageView {
|
|||||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||||
ViewUtil.setPaddingEnd(this, 0);
|
ViewUtil.setPaddingEnd(this, 0);
|
||||||
clearAnimation();
|
clearAnimation();
|
||||||
setImageResource(R.drawable.ic_delivery_status_read);
|
setImageResource(R.drawable.symbol_messagestatus_read_24);
|
||||||
updateContentDescription();
|
updateContentDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTint(int color) {
|
public void setTint(int color) {
|
||||||
setColorFilter(color);
|
setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateContentDescription() {
|
private void updateContentDescription() {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.PorterDuffColorFilter;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
@@ -15,11 +14,10 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.ContextUtil;
|
import org.thoughtcrime.securesms.util.ContextUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.DrawableUtil;
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class FromTextView extends SimpleEmojiTextView {
|
public class FromTextView extends SimpleEmojiTextView {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(FromTextView.class);
|
private static final String TAG = Log.tag(FromTextView.class);
|
||||||
@@ -71,17 +69,23 @@ public class FromTextView extends SimpleEmojiTextView {
|
|||||||
|
|
||||||
setText(builder);
|
setText(builder);
|
||||||
|
|
||||||
if (recipient.isBlocked()) setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_block_grey600_18dp, 0, 0, 0);
|
if (recipient.isBlocked()) setCompoundDrawablesRelativeWithIntrinsicBounds(getBlocked(), null, null, null);
|
||||||
else if (recipient.isMuted()) setCompoundDrawablesRelativeWithIntrinsicBounds(getMuted(), null, null, null);
|
else if (recipient.isMuted()) setCompoundDrawablesRelativeWithIntrinsicBounds(getMuted(), null, null, null);
|
||||||
else setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
|
else setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Drawable getBlocked() {
|
||||||
|
return getDrawable(R.drawable.symbol_block_16);
|
||||||
|
}
|
||||||
|
|
||||||
private Drawable getMuted() {
|
private Drawable getMuted() {
|
||||||
Drawable mutedDrawable = Objects.requireNonNull(ContextCompat.getDrawable(getContext(), R.drawable.ic_bell_disabled_16));
|
return getDrawable(R.drawable.ic_bell_disabled_16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable getDrawable(@DrawableRes int drawable) {
|
||||||
|
Drawable mutedDrawable = ContextUtil.requireDrawable(getContext(), drawable);
|
||||||
mutedDrawable.setBounds(0, 0, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18));
|
mutedDrawable.setBounds(0, 0, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18));
|
||||||
mutedDrawable.setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(getContext(), R.color.signal_icon_tint_secondary), PorterDuff.Mode.SRC_IN));
|
DrawableUtil.tint(mutedDrawable, ContextCompat.getColor(getContext(), R.color.signal_icon_tint_secondary));
|
||||||
|
|
||||||
return mutedDrawable;
|
return mutedDrawable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import androidx.lifecycle.Observer;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
import org.signal.core.util.ThreadUtil;
|
||||||
@@ -59,8 +61,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
@@ -184,7 +184,7 @@ public class InputPanel extends ConstraintLayout
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stickerSuggestionAdapter = new ConversationStickerSuggestionAdapter(GlideApp.with(this), this);
|
stickerSuggestionAdapter = new ConversationStickerSuggestionAdapter(Glide.with(this), this);
|
||||||
|
|
||||||
stickerSuggestion.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
|
stickerSuggestion.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
|
||||||
stickerSuggestion.setAdapter(stickerSuggestionAdapter);
|
stickerSuggestion.setAdapter(stickerSuggestionAdapter);
|
||||||
@@ -212,14 +212,14 @@ public class InputPanel extends ConstraintLayout
|
|||||||
composeText.setMediaListener(listener);
|
composeText.setMediaListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setQuote(@NonNull GlideRequests glideRequests,
|
public void setQuote(@NonNull RequestManager requestManager,
|
||||||
long id,
|
long id,
|
||||||
@NonNull Recipient author,
|
@NonNull Recipient author,
|
||||||
@Nullable CharSequence body,
|
@Nullable CharSequence body,
|
||||||
@NonNull SlideDeck attachments,
|
@NonNull SlideDeck attachments,
|
||||||
@NonNull QuoteModel.Type quoteType)
|
@NonNull QuoteModel.Type quoteType)
|
||||||
{
|
{
|
||||||
this.quoteView.setQuote(glideRequests, id, author, body, false, attachments, null, quoteType);
|
this.quoteView.setQuote(requestManager, id, author, body, false, attachments, null, quoteType);
|
||||||
|
|
||||||
int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight()
|
int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight()
|
||||||
: 0;
|
: 0;
|
||||||
@@ -325,10 +325,10 @@ public class InputPanel extends ConstraintLayout
|
|||||||
this.linkPreview.setNoPreview(customError);
|
this.linkPreview.setNoPreview(customError);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull Optional<LinkPreview> preview) {
|
public void setLinkPreview(@NonNull RequestManager requestManager, @NonNull Optional<LinkPreview> preview) {
|
||||||
if (preview.isPresent()) {
|
if (preview.isPresent()) {
|
||||||
this.linkPreview.setVisibility(View.VISIBLE);
|
this.linkPreview.setVisibility(View.VISIBLE);
|
||||||
this.linkPreview.setLinkPreview(glideRequests, preview.get(), true);
|
this.linkPreview.setLinkPreview(requestManager, preview.get(), true);
|
||||||
} else {
|
} else {
|
||||||
this.linkPreview.setVisibility(View.GONE);
|
this.linkPreview.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -404,7 +404,7 @@ public class InputPanel extends ConstraintLayout
|
|||||||
quoteView.setWallpaperEnabled(enabled);
|
quoteView.setWallpaperEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enterEditMessageMode(@NonNull GlideRequests glideRequests, @NonNull ConversationMessage conversationMessageToEdit, boolean fromDraft) {
|
public void enterEditMessageMode(@NonNull RequestManager requestManager, @NonNull ConversationMessage conversationMessageToEdit, boolean fromDraft) {
|
||||||
SpannableString textToEdit = conversationMessageToEdit.getDisplayBody(getContext());
|
SpannableString textToEdit = conversationMessageToEdit.getDisplayBody(getContext());
|
||||||
if (!fromDraft) {
|
if (!fromDraft) {
|
||||||
MessageStyler.convertSpoilersToComposeMode(textToEdit);
|
MessageStyler.convertSpoilersToComposeMode(textToEdit);
|
||||||
@@ -415,14 +415,14 @@ public class InputPanel extends ConstraintLayout
|
|||||||
if (quote == null) {
|
if (quote == null) {
|
||||||
clearQuote();
|
clearQuote();
|
||||||
} else {
|
} else {
|
||||||
setQuote(glideRequests, quote.getId(), Recipient.resolved(quote.getAuthor()), quote.getDisplayText(), quote.getAttachment(), quote.getQuoteType());
|
setQuote(requestManager, quote.getId(), Recipient.resolved(quote.getAuthor()), quote.getDisplayText(), quote.getAttachment(), quote.getQuoteType());
|
||||||
}
|
}
|
||||||
this.messageToEdit = conversationMessageToEdit.getMessageRecord();
|
this.messageToEdit = conversationMessageToEdit.getMessageRecord();
|
||||||
updateEditModeThumbnail(glideRequests);
|
updateEditModeThumbnail(requestManager);
|
||||||
updateEditModeUi();
|
updateEditModeUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEditModeThumbnail(@NonNull GlideRequests glideRequests) {
|
private void updateEditModeThumbnail(@NonNull RequestManager requestManager) {
|
||||||
if (messageToEdit instanceof MmsMessageRecord) {
|
if (messageToEdit instanceof MmsMessageRecord) {
|
||||||
MmsMessageRecord mediaEditMessage = (MmsMessageRecord) messageToEdit;
|
MmsMessageRecord mediaEditMessage = (MmsMessageRecord) messageToEdit;
|
||||||
SlideDeck slideDeck = mediaEditMessage.getSlideDeck();
|
SlideDeck slideDeck = mediaEditMessage.getSlideDeck();
|
||||||
@@ -430,7 +430,7 @@ public class InputPanel extends ConstraintLayout
|
|||||||
|
|
||||||
if (imageVideoSlide != null && imageVideoSlide.getUri() != null) {
|
if (imageVideoSlide != null && imageVideoSlide.getUri() != null) {
|
||||||
editMessageThumbnail.setVisibility(VISIBLE);
|
editMessageThumbnail.setVisibility(VISIBLE);
|
||||||
glideRequests.load(new DecryptableStreamUriLoader.DecryptableUri(imageVideoSlide.getUri()))
|
requestManager.load(new DecryptableStreamUriLoader.DecryptableUri(imageVideoSlide.getUri()))
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.into(editMessageThumbnail);
|
.into(editMessageThumbnail);
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.signal.ringrtc.CallLinkRootKey;
|
import org.signal.ringrtc.CallLinkRootKey;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash;
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@@ -162,11 +163,11 @@ public class LinkPreviewView extends FrameLayout {
|
|||||||
noPreview.setText(getLinkPreviewErrorString(customError));
|
noPreview.setText(getLinkPreviewErrorString(customError));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
public void setLinkPreview(@NonNull RequestManager requestManager, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
||||||
setLinkPreview(glideRequests, linkPreview, showThumbnail, true, false);
|
setLinkPreview(requestManager, linkPreview, showThumbnail, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showDescription, boolean scheduleMessageMode) {
|
public void setLinkPreview(@NonNull RequestManager requestManager, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showDescription, boolean scheduleMessageMode) {
|
||||||
spinner.setVisibility(GONE);
|
spinner.setVisibility(GONE);
|
||||||
noPreview.setVisibility(GONE);
|
noPreview.setVisibility(GONE);
|
||||||
|
|
||||||
@@ -216,13 +217,13 @@ public class LinkPreviewView extends FrameLayout {
|
|||||||
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
|
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
|
||||||
thumbnail.setVisibility(VISIBLE);
|
thumbnail.setVisibility(VISIBLE);
|
||||||
thumbnailState.applyState(thumbnail);
|
thumbnailState.applyState(thumbnail);
|
||||||
thumbnail.get().setImageResource(glideRequests, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION && !scheduleMessageMode, false);
|
thumbnail.get().setImageResource(requestManager, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION && !scheduleMessageMode, false);
|
||||||
thumbnail.get().showSecondaryText(false);
|
thumbnail.get().showSecondaryText(false);
|
||||||
} else if (callLinkRootKey != null) {
|
} else if (callLinkRootKey != null) {
|
||||||
thumbnail.setVisibility(VISIBLE);
|
thumbnail.setVisibility(VISIBLE);
|
||||||
thumbnailState.applyState(thumbnail);
|
thumbnailState.applyState(thumbnail);
|
||||||
thumbnail.get().setImageDrawable(
|
thumbnail.get().setImageDrawable(
|
||||||
glideRequests,
|
requestManager,
|
||||||
Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER
|
Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER
|
||||||
.getPhotoForCallLink()
|
.getPhotoForCallLink()
|
||||||
.asDrawable(getContext(),
|
.asDrawable(getContext(),
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ data class LinkPreviewViewThumbnailState(
|
|||||||
fun applyState(thumbnail: Stub<OutlinedThumbnailView>) {
|
fun applyState(thumbnail: Stub<OutlinedThumbnailView>) {
|
||||||
if (thumbnail.resolved()) {
|
if (thumbnail.resolved()) {
|
||||||
thumbnail.get().setCorners(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
thumbnail.get().setCorners(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||||
thumbnail.get().setDownloadClickListener(downloadListener)
|
thumbnail.get().setStartTransferClickListener(downloadListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -13,16 +13,19 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.databinding.PromptBatterySaverBottomSheetBinding
|
import org.thoughtcrime.securesms.databinding.PromptBatterySaverBottomSheetBinding
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||||
|
import org.thoughtcrime.securesms.util.LocalMetrics
|
||||||
import org.thoughtcrime.securesms.util.PowerManagerCompat
|
import org.thoughtcrime.securesms.util.PowerManagerCompat
|
||||||
|
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
|
class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG = Log.tag(PromptBatterySaverDialogFragment::class.java)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun show(fragmentManager: FragmentManager) {
|
fun show(fragmentManager: FragmentManager) {
|
||||||
@@ -51,8 +54,11 @@ class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFrag
|
|||||||
|
|
||||||
binding.continueButton.setOnClickListener {
|
binding.continueButton.setOnClickListener {
|
||||||
PowerManagerCompat.requestIgnoreBatteryOptimizations(requireContext())
|
PowerManagerCompat.requestIgnoreBatteryOptimizations(requireContext())
|
||||||
|
Log.i(TAG, "Requested to ignore battery optimizations, clearing local metrics.")
|
||||||
|
LocalMetrics.clear()
|
||||||
}
|
}
|
||||||
binding.dismissButton.setOnClickListener {
|
binding.dismissButton.setOnClickListener {
|
||||||
|
Log.i(TAG, "User denied request to ignore battery optimizations.")
|
||||||
SignalStore.uiHints().markDismissedBatterySaverPrompt()
|
SignalStore.uiHints().markDismissedBatterySaverPrompt()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.google.android.material.imageview.ShapeableImageView;
|
import com.google.android.material.imageview.ShapeableImageView;
|
||||||
import com.google.android.material.shape.CornerFamily;
|
import com.google.android.material.shape.CornerFamily;
|
||||||
@@ -32,7 +33,6 @@ import org.thoughtcrime.securesms.conversation.MessageStyler;
|
|||||||
import org.thoughtcrime.securesms.database.model.Mention;
|
import org.thoughtcrime.securesms.database.model.Mention;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
@@ -193,7 +193,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setQuote(GlideRequests glideRequests,
|
public void setQuote(RequestManager requestManager,
|
||||||
long id,
|
long id,
|
||||||
@NonNull Recipient author,
|
@NonNull Recipient author,
|
||||||
@Nullable CharSequence body,
|
@Nullable CharSequence body,
|
||||||
@@ -213,7 +213,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
this.author.observeForever(this);
|
this.author.observeForever(this);
|
||||||
setQuoteAuthor(author);
|
setQuoteAuthor(author);
|
||||||
setQuoteText(resolveBody(body, quoteType), attachments, originalMissing, storyReaction);
|
setQuoteText(resolveBody(body, quoteType), attachments, originalMissing, storyReaction);
|
||||||
setQuoteAttachment(glideRequests, body, attachments, originalMissing);
|
setQuoteAttachment(requestManager, body, attachments, originalMissing);
|
||||||
setQuoteMissingFooter(originalMissing);
|
setQuoteMissingFooter(originalMissing);
|
||||||
applyColorTheme();
|
applyColorTheme();
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull CharSequence body, @NonNull SlideDeck slideDeck, boolean originalMissing) {
|
private void setQuoteAttachment(@NonNull RequestManager requestManager, @NonNull CharSequence body, @NonNull SlideDeck slideDeck, boolean originalMissing) {
|
||||||
boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING;
|
boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING;
|
||||||
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW;
|
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW;
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
attachmentVideoOVerlayStub.setVisibility(GONE);
|
attachmentVideoOVerlayStub.setVisibility(GONE);
|
||||||
attachmentNameViewStub.setVisibility(GONE);
|
attachmentNameViewStub.setVisibility(GONE);
|
||||||
thumbnailView.setVisibility(VISIBLE);
|
thumbnailView.setVisibility(VISIBLE);
|
||||||
glideRequests.load(model)
|
requestManager.load(model)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.override(thumbWidth, thumbHeight)
|
.override(thumbWidth, thumbHeight)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
@@ -377,7 +377,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
attachmentVideoOVerlayStub.setVisibility(GONE);
|
attachmentVideoOVerlayStub.setVisibility(GONE);
|
||||||
attachmentNameViewStub.setVisibility(GONE);
|
attachmentNameViewStub.setVisibility(GONE);
|
||||||
thumbnailView.setVisibility(VISIBLE);
|
thumbnailView.setVisibility(VISIBLE);
|
||||||
glideRequests.load(R.drawable.ic_gift_thumbnail)
|
requestManager.load(R.drawable.ic_gift_thumbnail)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.override(thumbWidth, thumbHeight)
|
.override(thumbWidth, thumbHeight)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
@@ -404,7 +404,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
|||||||
if (imageVideoSlide.hasVideo() && !imageVideoSlide.isVideoGif()) {
|
if (imageVideoSlide.hasVideo() && !imageVideoSlide.isVideoGif()) {
|
||||||
attachmentVideoOVerlayStub.setVisibility(VISIBLE);
|
attachmentVideoOVerlayStub.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
glideRequests.load(new DecryptableUri(imageVideoSlide.getUri()))
|
requestManager.load(new DecryptableUri(imageVideoSlide.getUri()))
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.override(thumbWidth, thumbHeight)
|
.override(thumbWidth, thumbHeight)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.Key;
|
import com.bumptech.glide.load.Key;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||||
@@ -31,7 +32,6 @@ import org.signal.core.util.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
|
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
|
|
||||||
public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks<Cursor> {
|
public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
|||||||
|
|
||||||
Key signature = new MediaStoreSignature(mimeType, dateModified, orientation);
|
Key signature = new MediaStoreSignature(mimeType, dateModified, orientation);
|
||||||
|
|
||||||
GlideApp.with(getContext().getApplicationContext())
|
Glide.with(getContext().getApplicationContext())
|
||||||
.load(uri)
|
.load(uri)
|
||||||
.signature(signature)
|
.signature(signature)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
@@ -24,7 +25,6 @@ import org.thoughtcrime.securesms.contactshare.Contact;
|
|||||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||||
@@ -45,13 +45,13 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
|||||||
private TextView actionButtonView;
|
private TextView actionButtonView;
|
||||||
private ConversationItemFooter footer;
|
private ConversationItemFooter footer;
|
||||||
|
|
||||||
private Contact contact;
|
private Contact contact;
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
private GlideRequests glideRequests;
|
private RequestManager requestManager;
|
||||||
private EventListener eventListener;
|
private EventListener eventListener;
|
||||||
private CornerMask cornerMask;
|
private CornerMask cornerMask;
|
||||||
private int bigCornerRadius;
|
private int bigCornerRadius;
|
||||||
private int smallCornerRadius;
|
private int smallCornerRadius;
|
||||||
|
|
||||||
private final Map<RecipientId, LiveRecipient> activeRecipients = new HashMap<>();
|
private final Map<RecipientId, LiveRecipient> activeRecipients = new HashMap<>();
|
||||||
|
|
||||||
@@ -111,10 +111,10 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
|||||||
cornerMask.mask(canvas);
|
cornerMask.mask(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContact(@NonNull Contact contact, @NonNull GlideRequests glideRequests, @NonNull Locale locale) {
|
public void setContact(@NonNull Contact contact, @NonNull RequestManager requestManager, @NonNull Locale locale) {
|
||||||
this.glideRequests = glideRequests;
|
this.requestManager = requestManager;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
|
|
||||||
Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeForeverObserver(this));
|
Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeForeverObserver(this));
|
||||||
this.activeRecipients.clear();
|
this.activeRecipients.clear();
|
||||||
@@ -172,17 +172,17 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
|||||||
|
|
||||||
private void presentAvatar(@Nullable Uri uri) {
|
private void presentAvatar(@Nullable Uri uri) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
glideRequests.load(new DecryptableUri(uri))
|
requestManager.load(new DecryptableUri(uri))
|
||||||
.fallback(R.drawable.ic_contact_picture)
|
.fallback(R.drawable.symbol_person_display_40)
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.into(avatarView);
|
.into(avatarView);
|
||||||
} else {
|
} else {
|
||||||
glideRequests.load(R.drawable.ic_contact_picture)
|
requestManager.load(R.drawable.symbol_person_display_40)
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.into(avatarView);
|
.into(avatarView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components;
|
|||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -15,11 +14,12 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MediaTable;
|
import org.thoughtcrime.securesms.database.MediaTable;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ public class ThreadPhotoRailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMediaRecords(@NonNull GlideRequests glideRequests, @NonNull List<MediaTable.MediaRecord> mediaRecords) {
|
public void setMediaRecords(@NonNull RequestManager requestManager, @NonNull List<MediaTable.MediaRecord> mediaRecords) {
|
||||||
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), glideRequests, mediaRecords, this.listener));
|
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), requestManager, mediaRecords, this.listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ThreadPhotoRailAdapter extends RecyclerView.Adapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
|
private static class ThreadPhotoRailAdapter extends RecyclerView.Adapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
|
||||||
@@ -67,18 +67,18 @@ public class ThreadPhotoRailView extends FrameLayout {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(ThreadPhotoRailAdapter.class);
|
private static final String TAG = Log.tag(ThreadPhotoRailAdapter.class);
|
||||||
|
|
||||||
@NonNull private final GlideRequests glideRequests;
|
@NonNull private final RequestManager requestManager;
|
||||||
|
|
||||||
@Nullable private OnItemClickedListener clickedListener;
|
@Nullable private OnItemClickedListener clickedListener;
|
||||||
|
|
||||||
private final List<MediaTable.MediaRecord> mediaRecords = new ArrayList<>();
|
private final List<MediaTable.MediaRecord> mediaRecords = new ArrayList<>();
|
||||||
|
|
||||||
private ThreadPhotoRailAdapter(@NonNull Context context,
|
private ThreadPhotoRailAdapter(@NonNull Context context,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull RequestManager requestManager,
|
||||||
@NonNull List<MediaTable.MediaRecord> mediaRecords,
|
@NonNull List<MediaTable.MediaRecord> mediaRecords,
|
||||||
@Nullable OnItemClickedListener listener)
|
@Nullable OnItemClickedListener listener)
|
||||||
{
|
{
|
||||||
this.glideRequests = glideRequests;
|
this.requestManager = requestManager;
|
||||||
this.clickedListener = listener;
|
this.clickedListener = listener;
|
||||||
|
|
||||||
this.mediaRecords.clear();
|
this.mediaRecords.clear();
|
||||||
@@ -103,7 +103,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
|||||||
MediaTable.MediaRecord mediaRecord = mediaRecords.get(position);
|
MediaTable.MediaRecord mediaRecord = mediaRecords.get(position);
|
||||||
Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment());
|
Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment());
|
||||||
|
|
||||||
viewHolder.imageView.setImageResource(glideRequests, slide, false, false);
|
viewHolder.imageView.setImageResource(requestManager, slide, false, false);
|
||||||
viewHolder.imageView.setOnClickListener(v -> {
|
viewHolder.imageView.setOnClickListener(v -> {
|
||||||
MediaPreviewCache.INSTANCE.setDrawable(viewHolder.imageView.getImageDrawable());
|
MediaPreviewCache.INSTANCE.setDrawable(viewHolder.imageView.getImageDrawable());
|
||||||
if (clickedListener != null) clickedListener.onItemClicked(viewHolder.imageView, mediaRecord);
|
if (clickedListener != null) clickedListener.onItemClicked(viewHolder.imageView, mediaRecord);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import androidx.annotation.UiThread;
|
|||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
|
||||||
import com.bumptech.glide.RequestBuilder;
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.request.Request;
|
import com.bumptech.glide.request.Request;
|
||||||
import com.bumptech.glide.request.RequestListener;
|
import com.bumptech.glide.request.RequestListener;
|
||||||
@@ -39,8 +40,6 @@ import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|||||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
@@ -86,12 +85,12 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
private final CornerMask cornerMask;
|
private final CornerMask cornerMask;
|
||||||
|
|
||||||
private final Stub<TransferControlView> transferControlViewStub;
|
private final Stub<TransferControlView> transferControlViewStub;
|
||||||
private SlideClickListener thumbnailClickListener = null;
|
private SlideClickListener thumbnailClickListener = null;
|
||||||
private SlidesClickedListener downloadClickListener = null;
|
private SlidesClickedListener startTransferClickListener = null;
|
||||||
private SlidesClickedListener cancelDownloadClickListener = null;
|
private SlidesClickedListener cancelTransferClickListener = null;
|
||||||
private SlideClickListener playVideoClickListener = null;
|
private SlideClickListener playVideoClickListener = null;
|
||||||
private Slide slide = null;
|
private Slide slide = null;
|
||||||
|
|
||||||
|
|
||||||
public ThumbnailView(Context context) {
|
public ThumbnailView(Context context) {
|
||||||
@@ -314,23 +313,23 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageDrawable(@NonNull GlideRequests glideRequests, @Nullable Drawable drawable) {
|
public void setImageDrawable(@NonNull RequestManager requestManager, @Nullable Drawable drawable) {
|
||||||
glideRequests.clear(image);
|
requestManager.clear(image);
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
|
|
||||||
image.setImageDrawable(drawable);
|
image.setImageDrawable(drawable);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Slide slide,
|
||||||
boolean showControls, boolean isPreview)
|
boolean showControls, boolean isPreview)
|
||||||
{
|
{
|
||||||
return setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
|
return setImageResource(requestManager, slide, showControls, isPreview, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Slide slide,
|
||||||
boolean showControls, boolean isPreview,
|
boolean showControls, boolean isPreview,
|
||||||
int naturalWidth, int naturalHeight)
|
int naturalWidth, int naturalHeight)
|
||||||
{
|
{
|
||||||
@@ -340,10 +339,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
transferControlViewStub.setVisibility(View.GONE);
|
transferControlViewStub.setVisibility(View.GONE);
|
||||||
playOverlay.setVisibility(View.GONE);
|
playOverlay.setVisibility(View.GONE);
|
||||||
|
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
|
|
||||||
glideRequests.clear(image);
|
requestManager.clear(image);
|
||||||
image.setImageDrawable(null);
|
image.setImageDrawable(null);
|
||||||
|
|
||||||
int errorImageResource;
|
int errorImageResource;
|
||||||
@@ -363,7 +362,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showControls) {
|
if (showControls) {
|
||||||
transferControlViewStub.get().setDownloadClickListener(new DownloadClickDispatcher());
|
transferControlViewStub.get().setTransferClickListener(new DownloadClickDispatcher());
|
||||||
transferControlViewStub.get().setCancelClickListener(new CancelClickDispatcher());
|
transferControlViewStub.get().setCancelClickListener(new CancelClickDispatcher());
|
||||||
if (MediaUtil.isInstantVideoSupported(slide)) {
|
if (MediaUtil.isInstantVideoSupported(slide)) {
|
||||||
transferControlViewStub.get().setInstantPlaybackClickListener(new InstantVideoClickDispatcher());
|
transferControlViewStub.get().setInstantPlaybackClickListener(new InstantVideoClickDispatcher());
|
||||||
@@ -397,7 +396,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
Log.i(TAG, "loading part with id " + slide.asAttachment().getUri()
|
Log.i(TAG, "loading part with id " + slide.asAttachment().getUri()
|
||||||
+ ", progress " + slide.getTransferState() + ", fast preflight id: " +
|
+ ", progress " + slide.getTransferState() + ", fast preflight id: " +
|
||||||
slide.asAttachment().getFastPreflightId());
|
slide.asAttachment().fastPreflightId);
|
||||||
|
|
||||||
BlurHash previousBlurHash = this.slide != null ? this.slide.getPlaceholderBlur() : null;
|
BlurHash previousBlurHash = this.slide != null ? this.slide.getPlaceholderBlur() : null;
|
||||||
|
|
||||||
@@ -414,10 +413,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
boolean resultHandled = false;
|
boolean resultHandled = false;
|
||||||
|
|
||||||
if (slide.hasPlaceholder() && (previousBlurHash == null || !Objects.equals(slide.getPlaceholderBlur(), previousBlurHash))) {
|
if (slide.hasPlaceholder() && (previousBlurHash == null || !Objects.equals(slide.getPlaceholderBlur(), previousBlurHash))) {
|
||||||
buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(blurHash, result));
|
buildPlaceholderRequestBuilder(requestManager, slide).into(new GlideBitmapListeningTarget(blurHash, result));
|
||||||
resultHandled = true;
|
resultHandled = true;
|
||||||
} else if (!slide.hasPlaceholder()) {
|
} else if (!slide.hasPlaceholder()) {
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,14 +424,14 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
if (!MediaUtil.isJpegType(slide.getContentType()) && !MediaUtil.isVideoType(slide.getContentType())) {
|
if (!MediaUtil.isJpegType(slide.getContentType()) && !MediaUtil.isVideoType(slide.getContentType())) {
|
||||||
SettableFuture<Boolean> thumbnailFuture = new SettableFuture<>();
|
SettableFuture<Boolean> thumbnailFuture = new SettableFuture<>();
|
||||||
thumbnailFuture.deferTo(result);
|
thumbnailFuture.deferTo(result);
|
||||||
thumbnailFuture.addListener(new BlurHashClearListener(glideRequests, blurHash));
|
thumbnailFuture.addListener(new BlurHashClearListener(requestManager, blurHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result));
|
buildThumbnailRequestBuilder(requestManager, slide).into(new GlideDrawableListeningTarget(image, result));
|
||||||
|
|
||||||
resultHandled = true;
|
resultHandled = true;
|
||||||
} else {
|
} else {
|
||||||
glideRequests.clear(image);
|
requestManager.clear(image);
|
||||||
image.setImageDrawable(null);
|
image.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,20 +442,20 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Uri uri) {
|
||||||
return setImageResource(glideRequests, uri, 0, 0);
|
return setImageResource(requestManager, uri, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height) {
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Uri uri, int width, int height) {
|
||||||
return setImageResource(glideRequests, uri, width, height, true, null);
|
return setImageResource(requestManager, uri, width, height, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height, boolean animate, @Nullable ThumbnailRequestListener listener) {
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Uri uri, int width, int height, boolean animate, @Nullable ThumbnailRequestListener listener) {
|
||||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||||
|
|
||||||
transferControlViewStub.setVisibility(View.GONE);
|
transferControlViewStub.setVisibility(View.GONE);
|
||||||
|
|
||||||
GlideRequest<Drawable> request = glideRequests.load(new DecryptableUri(uri))
|
RequestBuilder<Drawable> request = requestManager.load(new DecryptableUri(uri))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||||
.listener(listener);
|
.listener(listener);
|
||||||
@@ -483,12 +482,12 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull StoryTextPostModel model, int width, int height) {
|
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull StoryTextPostModel model, int width, int height) {
|
||||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||||
|
|
||||||
transferControlViewStub.setVisibility(View.GONE);
|
transferControlViewStub.setVisibility(View.GONE);
|
||||||
|
|
||||||
GlideRequest<Drawable> request = glideRequests.load(model)
|
RequestBuilder<Drawable> request = requestManager.load(model)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.placeholder(model.getPlaceholder())
|
.placeholder(model.getPlaceholder())
|
||||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||||
@@ -502,7 +501,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> GlideRequest<T> override(@NonNull GlideRequest<T> request, int width, int height) {
|
private <T> RequestBuilder<T> override(@NonNull RequestBuilder<T> request, int width, int height) {
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
Log.d(TAG, "override: apply w" + width + "xh" + height);
|
Log.d(TAG, "override: apply w" + width + "xh" + height);
|
||||||
return request.override(width, height);
|
return request.override(width, height);
|
||||||
@@ -516,12 +515,12 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
this.thumbnailClickListener = listener;
|
this.thumbnailClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDownloadClickListener(SlidesClickedListener listener) {
|
public void setStartTransferClickListener(SlidesClickedListener listener) {
|
||||||
this.downloadClickListener = listener;
|
this.startTransferClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCancelDownloadClickListener(SlidesClickedListener listener) {
|
public void setCancelTransferClickListener(SlidesClickedListener listener) {
|
||||||
this.cancelDownloadClickListener = listener;
|
this.cancelTransferClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayVideoClickListener(SlideClickListener listener) {
|
public void setPlayVideoClickListener(SlideClickListener listener) {
|
||||||
@@ -532,8 +531,8 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
if (Util.equals(slide, other)) {
|
if (Util.equals(slide, other)) {
|
||||||
|
|
||||||
if (slide != null && other != null) {
|
if (slide != null && other != null) {
|
||||||
byte[] digestLeft = slide.asAttachment().getDigest();
|
byte[] digestLeft = slide.asAttachment().remoteDigest;
|
||||||
byte[] digestRight = other.asAttachment().getDigest();
|
byte[] digestRight = other.asAttachment().remoteDigest;
|
||||||
|
|
||||||
return Arrays.equals(digestLeft, digestRight);
|
return Arrays.equals(digestLeft, digestRight);
|
||||||
}
|
}
|
||||||
@@ -542,8 +541,8 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GlideRequest<Drawable> buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
private RequestBuilder<Drawable> buildThumbnailRequestBuilder(@NonNull RequestManager requestManager, @NonNull Slide slide) {
|
||||||
GlideRequest<Drawable> request = applySizing(glideRequests.load(new DecryptableUri(Objects.requireNonNull(slide.getUri())))
|
RequestBuilder<Drawable> requestBuilder = applySizing(requestManager.load(new DecryptableUri(Objects.requireNonNull(slide.getUri())))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||||
.transition(withCrossFade()));
|
.transition(withCrossFade()));
|
||||||
@@ -551,21 +550,21 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
||||||
|
|
||||||
if (slide.isInProgress() || doNotShowMissingThumbnailImage) {
|
if (slide.isInProgress() || doNotShowMissingThumbnailImage) {
|
||||||
return request;
|
return requestBuilder;
|
||||||
} else {
|
} else {
|
||||||
return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
return requestBuilder.apply(RequestOptions.errorOf(R.drawable.missing_thumbnail));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(GlideRequests glideRequests) {
|
public void clear(RequestManager requestManager) {
|
||||||
glideRequests.clear(image);
|
requestManager.clear(image);
|
||||||
image.setImageDrawable(null);
|
image.setImageDrawable(null);
|
||||||
|
|
||||||
if (transferControlViewStub.resolved()) {
|
if (transferControlViewStub.resolved()) {
|
||||||
transferControlViewStub.get().clear();
|
transferControlViewStub.get().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
|
|
||||||
slide = null;
|
slide = null;
|
||||||
@@ -594,9 +593,9 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private RequestBuilder<Bitmap> buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
private RequestBuilder<Bitmap> buildPlaceholderRequestBuilder(@NonNull RequestManager requestManager, @NonNull Slide slide) {
|
||||||
GlideRequest<Bitmap> bitmap = glideRequests.asBitmap();
|
RequestBuilder<Bitmap> bitmap = requestManager.asBitmap();
|
||||||
BlurHash placeholderBlur = slide.getPlaceholderBlur();
|
BlurHash placeholderBlur = slide.getPlaceholderBlur();
|
||||||
|
|
||||||
if (placeholderBlur != null) {
|
if (placeholderBlur != null) {
|
||||||
bitmap = bitmap.load(placeholderBlur);
|
bitmap = bitmap.load(placeholderBlur);
|
||||||
@@ -604,7 +603,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
bitmap = bitmap.load(slide.getPlaceholderRes(getContext().getTheme()));
|
bitmap = bitmap.load(slide.getPlaceholderRes(getContext().getTheme()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final GlideRequest<Bitmap> resizedRequest = applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE));
|
final RequestBuilder<Bitmap> resizedRequest = applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE));
|
||||||
if (placeholderBlur != null) {
|
if (placeholderBlur != null) {
|
||||||
return resizedRequest.centerCrop();
|
return resizedRequest.centerCrop();
|
||||||
} else {
|
} else {
|
||||||
@@ -612,7 +611,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <TranscodeType> GlideRequest<TranscodeType> applySizing(@NonNull GlideRequest<TranscodeType> request) {
|
private <TranscodeType> RequestBuilder<TranscodeType> applySizing(@NonNull RequestBuilder<TranscodeType> request) {
|
||||||
int[] size = new int[2];
|
int[] size = new int[2];
|
||||||
fillTargetDimensions(size, dimens, bounds);
|
fillTargetDimensions(size, dimens, bounds);
|
||||||
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
||||||
@@ -667,10 +666,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
Log.i(TAG, "onClick() for download button");
|
Log.i(TAG, "onClick() for download button");
|
||||||
if (downloadClickListener != null && slide != null) {
|
if (startTransferClickListener != null && slide != null) {
|
||||||
downloadClickListener.onClick(view, Collections.singletonList(slide));
|
startTransferClickListener.onClick(view, Collections.singletonList(slide));
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + slide + " downloadClickListener: " + downloadClickListener);
|
Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + slide + " downloadClickListener: " + startTransferClickListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,10 +678,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
Log.i(TAG, "onClick() for cancel button");
|
Log.i(TAG, "onClick() for cancel button");
|
||||||
if (cancelDownloadClickListener != null && slide != null) {
|
if (cancelTransferClickListener != null && slide != null) {
|
||||||
cancelDownloadClickListener.onClick(view, Collections.singletonList(slide));
|
cancelTransferClickListener.onClick(view, Collections.singletonList(slide));
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received a cancel button click, but unable to execute it. slide: " + slide + " cancelDownloadClickListener: " + cancelDownloadClickListener);
|
Log.w(TAG, "Received a cancel button click, but unable to execute it. slide: " + slide + " cancelDownloadClickListener: " + cancelTransferClickListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,23 +700,23 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
private static class BlurHashClearListener implements ListenableFuture.Listener<Boolean> {
|
private static class BlurHashClearListener implements ListenableFuture.Listener<Boolean> {
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
private final RequestManager requestManager;
|
||||||
private final ImageView blurHash;
|
private final ImageView blurHash;
|
||||||
|
|
||||||
private BlurHashClearListener(@NonNull GlideRequests glideRequests, @NonNull ImageView blurHash) {
|
private BlurHashClearListener(@NonNull RequestManager requestManager, @NonNull ImageView blurHash) {
|
||||||
this.glideRequests = glideRequests;
|
this.requestManager = requestManager;
|
||||||
this.blurHash = blurHash;
|
this.blurHash = blurHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Boolean result) {
|
public void onSuccess(Boolean result) {
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(ExecutionException e) {
|
public void onFailure(ExecutionException e) {
|
||||||
glideRequests.clear(blurHash);
|
requestManager.clear(blurHash);
|
||||||
blurHash.setImageDrawable(null);
|
blurHash.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import androidx.annotation.Px;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable;
|
import com.google.android.material.shape.MaterialShapeDrawable;
|
||||||
import com.google.android.material.shape.ShapeAppearanceModel;
|
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||||
|
|
||||||
import org.signal.core.util.DimensionUnit;
|
import org.signal.core.util.DimensionUnit;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for creating simple tooltips to show throughout the app. Utilizes a popup window so you
|
* Class for creating simple tooltips to show throughout the app. Utilizes a popup window so you
|
||||||
@@ -101,7 +101,7 @@ public class TooltipPopup extends PopupWindow {
|
|||||||
if (iconGlideModel != null) {
|
if (iconGlideModel != null) {
|
||||||
ImageView iconView = getContentView().findViewById(R.id.tooltip_icon);
|
ImageView iconView = getContentView().findViewById(R.id.tooltip_icon);
|
||||||
iconView.setVisibility(View.VISIBLE);
|
iconView.setVisibility(View.VISIBLE);
|
||||||
GlideApp.with(anchor.getContext()).load(iconGlideModel).into(iconView);
|
Glide.with(anchor.getContext()).load(iconGlideModel).into(iconView);
|
||||||
}
|
}
|
||||||
|
|
||||||
setElevation(10);
|
setElevation(10);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.widget.FrameLayout;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||||
@@ -23,7 +24,6 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder;
|
import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder;
|
||||||
import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder;
|
import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.util.ActionRequestListener;
|
import org.thoughtcrime.securesms.util.ActionRequestListener;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||||
@@ -81,7 +81,7 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType, @NonNull Runnable onMediaReady)
|
public void setImageUri(@NonNull RequestManager requestManager, @NonNull Uri uri, @NonNull String contentType, @NonNull Runnable onMediaReady)
|
||||||
{
|
{
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
final int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
||||||
@@ -103,7 +103,7 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
|
|
||||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||||
Log.i(TAG, "Loading in standard image view...");
|
Log.i(TAG, "Loading in standard image view...");
|
||||||
setImageViewUri(glideRequests, uri, onMediaReady);
|
setImageViewUri(requestManager, uri, onMediaReady);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Loading in subsampling image view...");
|
Log.i(TAG, "Loading in subsampling image view...");
|
||||||
setSubsamplingImageViewUri(uri);
|
setSubsamplingImageViewUri(uri);
|
||||||
@@ -112,11 +112,11 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull Runnable onMediaReady) {
|
private void setImageViewUri(@NonNull RequestManager requestManager, @NonNull Uri uri, @NonNull Runnable onMediaReady) {
|
||||||
photoView.setVisibility(View.VISIBLE);
|
photoView.setVisibility(View.VISIBLE);
|
||||||
subsamplingImageView.setVisibility(View.GONE);
|
subsamplingImageView.setVisibility(View.GONE);
|
||||||
|
|
||||||
glideRequests.load(new DecryptableUri(uri))
|
requestManager.load(new DecryptableUri(uri))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.dontTransform()
|
.dontTransform()
|
||||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class EmojiImageView extends AppCompatImageView {
|
|||||||
setImageResource(R.drawable.ic_emoji);
|
setImageResource(R.drawable.ic_emoji);
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(EmojiProvider.getEmojiDrawable(getContext(), emoji, forceJumboEmoji));
|
setImageDrawable(EmojiProvider.getEmojiDrawable(getContext(), emoji, forceJumboEmoji));
|
||||||
|
setContentDescription(emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.core.view.GestureDetectorCompat;
|
|||||||
import androidx.core.view.ViewKt;
|
import androidx.core.view.ViewKt;
|
||||||
import androidx.core.widget.TextViewCompat;
|
import androidx.core.widget.TextViewCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||||
@@ -73,6 +74,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
private boolean isJumbomoji;
|
private boolean isJumbomoji;
|
||||||
private boolean forceJumboEmoji;
|
private boolean forceJumboEmoji;
|
||||||
private boolean renderSpoilers;
|
private boolean renderSpoilers;
|
||||||
|
private boolean shrinkWrap;
|
||||||
|
|
||||||
private MentionRendererDelegate mentionRendererDelegate;
|
private MentionRendererDelegate mentionRendererDelegate;
|
||||||
private SpoilerRendererDelegate spoilerRendererDelegate;
|
private SpoilerRendererDelegate spoilerRendererDelegate;
|
||||||
@@ -96,6 +98,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
measureLastLine = a.getBoolean(R.styleable.EmojiTextView_measureLastLine, false);
|
measureLastLine = a.getBoolean(R.styleable.EmojiTextView_measureLastLine, false);
|
||||||
forceJumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
|
forceJumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
|
||||||
renderSpoilers = a.getBoolean(R.styleable.EmojiTextView_emoji_renderSpoilers, false);
|
renderSpoilers = a.getBoolean(R.styleable.EmojiTextView_emoji_renderSpoilers, false);
|
||||||
|
shrinkWrap = a.getBoolean(R.styleable.EmojiTextView_emoji_shrinkWrap, false);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.textSize });
|
a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.textSize });
|
||||||
@@ -224,6 +227,25 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
||||||
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
|
int mode = MeasureSpec.getMode(widthMeasureSpec);
|
||||||
|
if (shrinkWrap && getLayout() != null && mode == MeasureSpec.AT_MOST) {
|
||||||
|
Layout layout = getLayout();
|
||||||
|
|
||||||
|
float maxLineWidth = 0f;
|
||||||
|
for (int i = 0; i < layout.getLineCount(); i++) {
|
||||||
|
if (layout.getLineWidth(i) > maxLineWidth) {
|
||||||
|
maxLineWidth = layout.getLineWidth(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int desiredWidth = (int) maxLineWidth + getPaddingLeft() + getPaddingRight();
|
||||||
|
if (getMeasuredWidth() > desiredWidth) {
|
||||||
|
widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, mode);
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CharSequence text = getText();
|
CharSequence text = getText();
|
||||||
if (getLayout() == null || !measureLastLine || text == null || text.length() == 0) {
|
if (getLayout() == null || !measureLastLine || text == null || text.length() == 0) {
|
||||||
lastLineWidth = -1;
|
lastLineWidth = -1;
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
|||||||
this.resendListener = resendListener;
|
this.resendListener = resendListener;
|
||||||
|
|
||||||
setTitle(R.string.UntrustedSendDialog_send_message);
|
setTitle(R.string.UntrustedSendDialog_send_message);
|
||||||
setIcon(R.drawable.ic_warning);
|
setIcon(R.drawable.symbol_error_triangle_fill_24);
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
setPositiveButton(R.string.UntrustedSendDialog_send, this);
|
setPositiveButton(R.string.UntrustedSendDialog_send, this);
|
||||||
setNegativeButton(android.R.string.cancel, null);
|
setNegativeButton(android.R.string.cancel, null);
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI
|
|||||||
this.resendListener = resendListener;
|
this.resendListener = resendListener;
|
||||||
|
|
||||||
setTitle(R.string.UnverifiedSendDialog_send_message);
|
setTitle(R.string.UnverifiedSendDialog_send_message);
|
||||||
setIcon(R.drawable.ic_warning);
|
setIcon(R.drawable.symbol_error_triangle_fill_24);
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
setPositiveButton(R.string.UnverifiedSendDialog_send, this);
|
setPositiveButton(R.string.UnverifiedSendDialog_send, this);
|
||||||
setNegativeButton(android.R.string.cancel, null);
|
setNegativeButton(android.R.string.cancel, null);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user