mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-14 05:53:19 +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 |
@@ -21,8 +21,8 @@ plugins {
|
||||
|
||||
apply(from = "static-ips.gradle.kts")
|
||||
|
||||
val canonicalVersionCode = 1373
|
||||
val canonicalVersionName = "6.43.2"
|
||||
val canonicalVersionCode = 1389
|
||||
val canonicalVersionName = "6.47.0"
|
||||
|
||||
val postFixSize = 100
|
||||
val abiPostFix: Map<String, Int> = mapOf(
|
||||
@@ -98,7 +98,6 @@ android {
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = signalKotlinJvmTarget
|
||||
freeCompilerArgs = listOf("-Xallow-result-return-type")
|
||||
}
|
||||
|
||||
keystores["debug"]?.let { properties ->
|
||||
@@ -164,8 +163,8 @@ android {
|
||||
versionCode = canonicalVersionCode * postFixSize
|
||||
versionName = canonicalVersionName
|
||||
|
||||
minSdkVersion(signalMinSdkVersion)
|
||||
targetSdkVersion(signalTargetSdkVersion)
|
||||
minSdk = signalMinSdkVersion
|
||||
targetSdk = signalTargetSdkVersion
|
||||
|
||||
multiDexEnabled = true
|
||||
|
||||
@@ -415,9 +414,7 @@ android {
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
|
||||
variant.outputs
|
||||
outputs
|
||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
if (output.baseName.contains("nightly")) {
|
||||
@@ -430,10 +427,10 @@ android {
|
||||
output.versionNameOverride = tag
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
||||
} else {
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
|
||||
}
|
||||
} 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 postFix: Int = abiPostFix[abiName]!!
|
||||
@@ -447,25 +444,20 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
android.variantFilter {
|
||||
val distribution: String = flavors[0].name
|
||||
val environment: String = flavors[1].name
|
||||
val buildType: String = buildType.name
|
||||
val fullName: String = distribution + environment.capitalize() + buildType.capitalize()
|
||||
|
||||
if (!selectableVariants.contains(fullName)) {
|
||||
ignore = true
|
||||
androidComponents {
|
||||
beforeVariants { variant ->
|
||||
variant.enable = variant.name in selectableVariants
|
||||
}
|
||||
}
|
||||
|
||||
android.buildTypes.forEach {
|
||||
val path: String = if (it.name == "release") {
|
||||
"$projectDir/src/release/java"
|
||||
} else {
|
||||
"$projectDir/src/debug/java"
|
||||
}
|
||||
val releaseDir = "$projectDir/src/release/java"
|
||||
val debugDir = "$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(":contacts"))
|
||||
implementation(project(":qr"))
|
||||
implementation(project(":sms-exporter"))
|
||||
implementation(project(":sticky-header-grid"))
|
||||
implementation(project(":photoview"))
|
||||
implementation(project(":glide-webp"))
|
||||
implementation(project(":core-ui"))
|
||||
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
@@ -507,16 +497,19 @@ dependencies {
|
||||
implementation(libs.androidx.exifinterface)
|
||||
implementation(libs.androidx.compose.rxjava3)
|
||||
implementation(libs.androidx.compose.runtime.livedata)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.multidex)
|
||||
implementation(libs.androidx.navigation.fragment.ktx)
|
||||
implementation(libs.androidx.navigation.ui.ktx)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||
implementation(libs.androidx.lifecycle.livedata.ktx)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
||||
implementation(libs.androidx.lifecycle.common.java8)
|
||||
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.camera.core)
|
||||
implementation(libs.androidx.camera.camera2)
|
||||
implementation(libs.androidx.camera.lifecycle)
|
||||
|
||||
@@ -255,7 +255,7 @@ class BackupTest {
|
||||
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
|
||||
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
|
||||
|
||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED
|
||||
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
|
||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
|
||||
|
||||
SignalStore.settings().isLinkPreviewsEnabled = false
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import com.bumptech.glide.RequestManager
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
@@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
@@ -209,8 +209,9 @@ class V2ConversationItemShapeTest {
|
||||
override val selectedItems: Set<MultiselectPart> = emptySet()
|
||||
override val isMessageRequestAccepted: Boolean = true
|
||||
override val searchQuery: String? = null
|
||||
override val glideRequests: GlideRequests = mockk()
|
||||
override val requestManager: RequestManager = mockk()
|
||||
override val isParentInScroll: Boolean = false
|
||||
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
|
||||
|
||||
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
||||
|
||||
@@ -321,5 +322,11 @@ class V2ConversationItemShapeTest {
|
||||
override fun onItemClick(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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ class AttachmentTableTest {
|
||||
false
|
||||
)
|
||||
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
||||
|
||||
assertNotEquals(attachment1Info, attachment2Info)
|
||||
}
|
||||
@@ -89,8 +89,8 @@ class AttachmentTableTest {
|
||||
true
|
||||
)
|
||||
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
||||
|
||||
assertNotEquals(attachment1Info, attachment2Info)
|
||||
}
|
||||
@@ -124,9 +124,9 @@ class AttachmentTableTest {
|
||||
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
|
||||
|
||||
// THEN
|
||||
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA)!!
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA_FILE)!!
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||
|
||||
assertNotEquals(standardInfo, highInfo)
|
||||
standardInfo.file assertIs previousInfo.file
|
||||
@@ -158,9 +158,9 @@ class AttachmentTableTest {
|
||||
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
||||
|
||||
// THEN
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
||||
|
||||
highInfo.file assertIsNot standardInfo.file
|
||||
secondHighInfo.file assertIs highInfo.file
|
||||
|
||||
@@ -214,6 +214,175 @@ class CallTableTest {
|
||||
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
|
||||
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
|
||||
val era = "aaa"
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
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.requireLong
|
||||
import org.signal.core.util.withinTransaction
|
||||
@@ -33,8 +33,8 @@ class GroupTableTest {
|
||||
fun setUp() {
|
||||
groupTable = SignalDatabase.groups
|
||||
|
||||
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
|
||||
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
|
||||
groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
|
||||
groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.signal.core.util.forEach
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
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.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
@@ -220,7 +220,7 @@ class LogDatabaseTest {
|
||||
)
|
||||
|
||||
db.writableDatabase
|
||||
.update(LogDatabase.CrashTable.TABLE_NAME)
|
||||
.updateAll(LogDatabase.CrashTable.TABLE_NAME)
|
||||
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
||||
.run()
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class RecipientTableTest {
|
||||
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||
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()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.signal.core.util.Base64
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.readToSingleBoolean
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
@@ -109,6 +110,18 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
val record = SignalDatabase.recipients.getRecord(id)
|
||||
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
|
||||
@@ -164,6 +177,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
expectPniVerified()
|
||||
}
|
||||
|
||||
test("no match, all fields") {
|
||||
@@ -225,6 +239,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectPniVerified()
|
||||
}
|
||||
|
||||
test("e164 and aci matches, all provided, new pni") {
|
||||
@@ -694,6 +710,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
|
||||
expectDeleted()
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectPniVerified()
|
||||
}
|
||||
|
||||
test("merge, e164+pni & aci, pni session, pni verified") {
|
||||
@@ -706,6 +724,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectThreadMergeEvent(E164_A)
|
||||
expectPniVerified()
|
||||
}
|
||||
|
||||
test("merge, e164+pni & e164+pni+aci, change number") {
|
||||
@@ -1037,6 +1056,10 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
if (!test.sessionSwitchoverExpected) {
|
||||
test.expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
if (!test.pniVerifiedExpected) {
|
||||
test.expectPniNotVerified()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e.javaClass != exception) {
|
||||
val error = java.lang.AssertionError("[$name] ${e.message}")
|
||||
@@ -1056,6 +1079,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
var changeNumberExpected = false
|
||||
var threadMergeExpected = false
|
||||
var sessionSwitchoverExpected = false
|
||||
var pniVerifiedExpected = false
|
||||
|
||||
init {
|
||||
// Need to delete these first to prevent foreign key crash
|
||||
@@ -1207,6 +1231,24 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
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 {
|
||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||
RecipientTable.TABLE_NAME,
|
||||
|
||||
@@ -290,7 +290,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
from = sender,
|
||||
timestamp = wallClock,
|
||||
groupId = groupId,
|
||||
groupContext = groupContext
|
||||
groupContext = groupContext,
|
||||
serverGuid = null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ class EditMessageSyncProcessorTest {
|
||||
)
|
||||
|
||||
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
|
||||
AttachmentTable.UNIQUE_ID,
|
||||
AttachmentTable.TRANSFER_FILE
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIsNotNull
|
||||
import org.thoughtcrime.securesms.testing.assertIsNull
|
||||
import org.thoughtcrime.securesms.testing.success
|
||||
import org.whispersystems.signalservice.api.util.Usernames
|
||||
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -58,7 +59,7 @@ class UsernameEditFragmentTest {
|
||||
|
||||
@Test
|
||||
fun testUsernameCreationInRegistration() {
|
||||
val scenario = createScenario(true)
|
||||
val scenario = createScenario(UsernameEditMode.REGISTRATION)
|
||||
|
||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||
|
||||
@@ -76,7 +77,7 @@ class UsernameEditFragmentTest {
|
||||
@Ignore("Flakey espresso test.")
|
||||
@Test
|
||||
fun testUsernameCreationOutsideOfRegistration() {
|
||||
val scenario = createScenario()
|
||||
val scenario = createScenario(UsernameEditMode.NORMAL)
|
||||
|
||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||
|
||||
@@ -96,7 +97,7 @@ class UsernameEditFragmentTest {
|
||||
fun testNicknameUpdateHappyPath() {
|
||||
val nickname = "Spiderman"
|
||||
val discriminator = "4578"
|
||||
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
|
||||
val username = "$nickname${Usernames.DELIMITER}$discriminator"
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
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)
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
|
||||
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
|
||||
private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
|
||||
val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
|
||||
return launchFragmentInContainer(
|
||||
fragmentArgs = fragmentArgs,
|
||||
themeResId = R.style.Signal_DayNight_NoActionBar
|
||||
|
||||
@@ -33,7 +33,7 @@ class ContactRecordProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun process_splitContact_normalSplit() {
|
||||
fun process_splitContact_normalSplit_twoRecords() {
|
||||
// GIVEN
|
||||
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||
setStorageId(originalId, STORAGE_ID_A)
|
||||
@@ -69,6 +69,35 @@ class ContactRecordProcessorTest {
|
||||
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
|
||||
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
|
||||
// GIVEN
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.testing
|
||||
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
import org.signal.core.util.deleteAll
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
@@ -34,7 +36,8 @@ class SignalDatabaseRule(
|
||||
|
||||
private fun deleteAllThreads() {
|
||||
if (deleteAllThreadsOnEachRun) {
|
||||
SignalDatabase.threads.clearForTests()
|
||||
SignalDatabase.threads.deleteAllConversations()
|
||||
SignalDatabase.rawDatabase.deleteAll(ThreadTable.TABLE_NAME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,9 @@ class ConversationElementGenerator {
|
||||
-1,
|
||||
null,
|
||||
null,
|
||||
0
|
||||
0,
|
||||
false,
|
||||
null
|
||||
)
|
||||
|
||||
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import com.bumptech.glide.Glide
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
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.mutiselect.MultiselectPart
|
||||
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.MessageRecord
|
||||
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.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
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?) {
|
||||
val adapter = ConversationAdapterV2(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
glideRequests = GlideApp.with(this),
|
||||
requestManager = Glide.with(this),
|
||||
clickListener = ClickListener(),
|
||||
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
||||
colorizer = Colorizer(),
|
||||
startExpirationTimeout = {}
|
||||
startExpirationTimeout = {},
|
||||
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) }
|
||||
)
|
||||
|
||||
if (springboardViewModel.hasWallpaper.value) {
|
||||
@@ -296,5 +298,17 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
||||
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.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_NUMBERS" />
|
||||
|
||||
@@ -160,12 +155,6 @@
|
||||
android:value=".MainActivity" />
|
||||
</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"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="true">
|
||||
@@ -693,7 +682,12 @@
|
||||
|
||||
<activity android:name=".NewConversationActivity"
|
||||
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:exported="false"/>
|
||||
|
||||
@@ -768,6 +762,13 @@
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
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
|
||||
android:name=".stories.settings.StorySettingsActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
@@ -1072,13 +1073,6 @@
|
||||
android:launchMode="singleTask"
|
||||
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"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
@@ -1100,24 +1094,11 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
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"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".exporter.SignalSmsExportService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".service.webrtc.WebRtcCallService"
|
||||
@@ -1396,6 +1377,16 @@
|
||||
</intent-filter>
|
||||
</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"/>
|
||||
|
||||
</application>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.gms.security.ProviderInstaller;
|
||||
|
||||
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.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupRingCleanupJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
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.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
@@ -177,7 +178,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
||||
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
|
||||
.addNonBlocking(() -> GlideApp.get(this))
|
||||
.addNonBlocking(() -> Glide.get(this))
|
||||
.addNonBlocking(this::cleanAvatarStorage)
|
||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||
@@ -214,6 +215,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
|
||||
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
||||
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
|
||||
.addPostRender(GroupRingCleanupJob::enqueue)
|
||||
.execute();
|
||||
|
||||
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.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
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.ProfileContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
@@ -96,7 +96,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||
|
||||
Resources resources = this.getResources();
|
||||
|
||||
GlideApp.with(this)
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(contactPhoto)
|
||||
.fallback(fallbackPhoto.asCallCard(this))
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.signal.ringrtc.CallLinkRootKey;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||
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.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
@@ -41,7 +42,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
@NonNull ConversationMessage messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull RequestManager requestManager,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MultiselectPart> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
@@ -122,5 +123,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
|
||||
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
|
||||
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.lifecycle.LifecycleOwner;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
@@ -14,7 +15,7 @@ public interface BindableConversationListItem extends Unbindable {
|
||||
|
||||
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull ThreadRecord thread,
|
||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||
@NonNull RequestManager requestManager, @NonNull Locale locale,
|
||||
@NonNull Set<Long> typingThreads,
|
||||
@NonNull ConversationSet selectedConversations);
|
||||
|
||||
|
||||
@@ -11,16 +11,36 @@ import androidx.lifecycle.Lifecycle;
|
||||
|
||||
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.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.
|
||||
*/
|
||||
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,
|
||||
@NonNull Lifecycle lifecycle,
|
||||
@@ -137,4 +157,37 @@ public final class BlockUnblockDialog {
|
||||
|
||||
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.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
|
||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DisplayMetricsUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@@ -99,6 +102,10 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
|
||||
private void initializeContactFilterView() {
|
||||
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() {
|
||||
|
||||
@@ -27,6 +27,8 @@ class ContactSelectionListAdapter(
|
||||
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(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> {
|
||||
@@ -44,6 +46,16 @@ class ContactSelectionListAdapter(
|
||||
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> {
|
||||
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 {
|
||||
|
||||
enum class ArbitraryRow(val code: String) {
|
||||
NEW_GROUP("new-group"),
|
||||
INVITE_TO_SIGNAL("invite-to-signal"),
|
||||
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 {
|
||||
fun fromCode(code: String) = values().first { it.code == code }
|
||||
@@ -120,6 +152,8 @@ class ContactSelectionListAdapter(
|
||||
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
||||
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
||||
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
||||
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
|
||||
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,5 +162,7 @@ class ContactSelectionListAdapter(
|
||||
fun onNewGroupClicked()
|
||||
fun onInviteToSignalClicked()
|
||||
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.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -141,6 +142,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
private ContactSearchMediator contactSearchMediator;
|
||||
|
||||
@Nullable private NewConversationCallback newConversationCallback;
|
||||
@Nullable private FindByCallback findByCallback;
|
||||
@Nullable private NewCallCallback newCallCallback;
|
||||
@Nullable private ScrollCallback scrollCallback;
|
||||
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
||||
@@ -161,6 +163,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
newConversationCallback = (NewConversationCallback) context;
|
||||
}
|
||||
|
||||
if (context instanceof FindByCallback) {
|
||||
findByCallback = (FindByCallback) context;
|
||||
}
|
||||
|
||||
if (context instanceof NewCallCallback) {
|
||||
newCallCallback = (NewCallCallback) context;
|
||||
}
|
||||
@@ -379,6 +385,16 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
newConversationCallback.onNewGroup(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindByPhoneNumberClicked() {
|
||||
findByCallback.onFindByPhoneNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindByUsernameClicked() {
|
||||
findByCallback.onFindByUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInviteToSignalClicked() {
|
||||
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 {
|
||||
public void onItemClick(ContactSearchKey contact) {
|
||||
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
||||
@@ -874,6 +894,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
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 (!hasQuery && includeRecents) {
|
||||
builder.addSection(new ContactSearchConfiguration.Section.Recents(
|
||||
@@ -891,7 +917,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
|
||||
includeSelf,
|
||||
transportType,
|
||||
newCallCallback == null,
|
||||
newCallCallback == null && findByCallback == null,
|
||||
null,
|
||||
!hideLetterHeaders()
|
||||
));
|
||||
@@ -1011,6 +1037,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
void onNewGroup(boolean forceV1);
|
||||
}
|
||||
|
||||
public interface FindByCallback {
|
||||
void onFindByUsername();
|
||||
|
||||
void onFindByPhoneNumber();
|
||||
}
|
||||
|
||||
public interface NewCallCallback {
|
||||
void onInvite();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||
.setOnDismissListener(dialog13 -> finish())
|
||||
.create();
|
||||
|
||||
dialog.setIcon(getResources().getDrawable(R.drawable.icon_dialog));
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.donations.StripeApi;
|
||||
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment;
|
||||
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.recipients.Recipient;
|
||||
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.views.SimpleProgressDialog;
|
||||
|
||||
@@ -70,14 +72,15 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class NewConversationActivity extends ContactSelectionActivity
|
||||
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener
|
||||
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener, ContactSelectionListFragment.FindByCallback
|
||||
{
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||
|
||||
private ContactsManagementViewModel viewModel;
|
||||
private ActivityResultLauncher<Intent> contactLauncher;
|
||||
private ContactsManagementViewModel viewModel;
|
||||
private ActivityResultLauncher<Intent> contactLauncher;
|
||||
private ActivityResultLauncher<FindByMode> findByLauncher;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -163,7 +172,12 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
}
|
||||
|
||||
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
|
||||
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
||||
.withDataUri(getIntent().getData())
|
||||
@@ -234,6 +248,16 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindByUsername() {
|
||||
findByLauncher.launch(FindByMode.USERNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindByPhoneNumber() {
|
||||
findByLauncher.launch(FindByMode.PHONE_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView) {
|
||||
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
|
||||
|
||||
@@ -374,7 +374,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
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.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
||||
@Override
|
||||
@@ -388,7 +388,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
public void 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);
|
||||
|
||||
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
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.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)
|
||||
.setCancelable(true)
|
||||
.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> {
|
||||
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
|
||||
DATABASE(DatabaseAttachment::class.java, "database"),
|
||||
MMS_NOTIFICATION(MmsNotificationAttachment::class.java, "mms_notification"),
|
||||
POINTER(PointerAttachment::class.java, "pointer"),
|
||||
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
|
||||
URI(UriAttachment::class.java, "uri")
|
||||
@@ -32,7 +31,6 @@ object AttachmentCreator : Parcelable.Creator<Attachment> {
|
||||
|
||||
return when (Subclass.values().first { rawCode == it.code }) {
|
||||
Subclass.DATABASE -> DatabaseAttachment(source)
|
||||
Subclass.MMS_NOTIFICATION -> MmsNotificationAttachment(source)
|
||||
Subclass.POINTER -> PointerAttachment(source)
|
||||
Subclass.TOMBSTONE -> TombstoneAttachment(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
||||
import org.thoughtcrime.securesms.media.MediaInput;
|
||||
import org.thoughtcrime.securesms.video.interfaces.MediaInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -8,12 +8,12 @@ import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.setPadding
|
||||
import com.airbnb.lottie.SimpleColorFilter
|
||||
import com.bumptech.glide.Glide
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.Avatar
|
||||
import org.thoughtcrime.securesms.avatar.AvatarRenderer
|
||||
import org.thoughtcrime.securesms.avatar.Avatars
|
||||
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.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
@@ -132,12 +132,12 @@ object AvatarPickerItem {
|
||||
}
|
||||
is Avatar.Photo -> {
|
||||
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 -> {
|
||||
imageView.setPadding((imageView.width * 0.2).toInt())
|
||||
textView.visible = false
|
||||
GlideApp.with(imageView).clear(imageView)
|
||||
Glide.with(imageView).clear(imageView)
|
||||
imageView.setImageResource(model.avatar.resourceId)
|
||||
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
|
||||
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
||||
|
||||
@@ -5,10 +5,10 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.res.use
|
||||
import com.bumptech.glide.RequestManager
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
@@ -76,7 +76,7 @@ class AvatarView @JvmOverloads constructor(
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ object BackupCountQueries {
|
||||
@get:JvmStatic
|
||||
val attachmentCount: String = """
|
||||
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
|
||||
"""
|
||||
}
|
||||
|
||||
@@ -119,8 +119,7 @@ class BackupFrameOutputStream extends FullBackupBase.BackupStream {
|
||||
try {
|
||||
write(outputStream, new BackupFrame.Builder()
|
||||
.attachment(new Attachment.Builder()
|
||||
.rowId(attachmentId.getRowId())
|
||||
.attachmentId(attachmentId.getUniqueId())
|
||||
.rowId(attachmentId.id)
|
||||
.length(Util.toIntExact(size))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.database.SessionTable;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyTable;
|
||||
import org.thoughtcrime.securesms.database.StickerTable;
|
||||
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -65,6 +64,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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 IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
|
||||
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),
|
||||
@@ -159,15 +160,15 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
for (String table : tables) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
@@ -444,11 +445,10 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
long estimatedCount)
|
||||
throws IOException
|
||||
{
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.ROW_ID));
|
||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.UNIQUE_ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.SIZE));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_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));
|
||||
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
@@ -457,14 +457,14 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
if (size <= 0 || fileLength != dbLength) {
|
||||
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));
|
||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||
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) {
|
||||
Log.w(TAG, "Missing attachment", e);
|
||||
}
|
||||
@@ -579,27 +579,34 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
return count;
|
||||
}
|
||||
|
||||
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0 &&
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.VIEW_ONCE)) <= 0;
|
||||
private static boolean isNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull Cursor cursor) {
|
||||
long id = CursorUtil.requireLong(cursor, MessageTable.ID);
|
||||
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) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0;
|
||||
}
|
||||
|
||||
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 };
|
||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long messageId) {
|
||||
String[] columns = new String[] { MessageTable.ID, MessageTable.EXPIRE_STARTED, MessageTable.EXPIRES_IN, MessageTable.LATEST_REVISION_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)) {
|
||||
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)
|
||||
throws IOException
|
||||
{
|
||||
File dataFile = AttachmentTable.newFile(context);
|
||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||
File dataFile = AttachmentTable.newFile(context);
|
||||
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();
|
||||
|
||||
try {
|
||||
inputStream.readAttachmentTo(output.second, attachment.length);
|
||||
|
||||
contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath());
|
||||
contentValues.put(AttachmentTable.DATA_RANDOM, output.first);
|
||||
contentValues.put(dataFileColumnName, dataFile.getAbsolutePath());
|
||||
contentValues.put(dataRandomColumnName, output.first);
|
||||
} catch (BackupRecordInputStream.BadMacException e) {
|
||||
Log.w(TAG, "Bad MAC for attachment " + attachment.attachmentId + "! Can't restore it.", e);
|
||||
dataFile.delete();
|
||||
contentValues.put(AttachmentTable.DATA, (String) null);
|
||||
contentValues.put(AttachmentTable.DATA_RANDOM, (String) null);
|
||||
contentValues.put(dataFileColumnName, (String) null);
|
||||
contentValues.put(dataRandomColumnName, (String) null);
|
||||
}
|
||||
|
||||
db.update(AttachmentTable.TABLE_NAME, contentValues,
|
||||
AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?",
|
||||
new String[] {String.valueOf(attachment.rowId), String.valueOf(attachment.attachmentId)});
|
||||
db.update(tableName,
|
||||
contentValues,
|
||||
idColumnName + " = ?",
|
||||
SqlUtil.buildArgs(attachment.rowId));
|
||||
}
|
||||
|
||||
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.recipients.RecipientId
|
||||
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.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
@@ -168,7 +167,7 @@ object BackupRepository {
|
||||
/**
|
||||
* Returns an object with details about the remote backup state.
|
||||
*/
|
||||
fun getRemoteBackupState(): NetworkResult<ArchiveGetBackupInfoResponse> {
|
||||
fun getRemoteBackupState(): NetworkResult<BackupMetadata> {
|
||||
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||
|
||||
@@ -182,6 +181,18 @@ object BackupRepository {
|
||||
}
|
||||
.then { 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 callIdToType = HashMap<Long, Long>()
|
||||
}
|
||||
|
||||
class BackupMetadata(
|
||||
val usedSpace: Long,
|
||||
val mediaCount: Long
|
||||
)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
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
|
||||
|
||||
fun AttachmentTable.clearAllDataForBackupRestore() {
|
||||
writableDatabase.delete(AttachmentTable.TABLE_NAME).run()
|
||||
writableDatabase.deleteAll(AttachmentTable.TABLE_NAME)
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ class CallLogIterator(private val cursor: Cursor) : Iterator<BackupCall?>, Close
|
||||
CallTable.Event.DECLINED -> Call.Event.DECLINED
|
||||
CallTable.Event.GENERIC_GROUP_CALL -> Call.Event.GENERIC_GROUP_CALL
|
||||
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.RINGING -> Call.Event.UNKNOWN_EVENT
|
||||
CallTable.Event.NOT_ACCEPTED -> Call.Event.NOT_ACCEPTED
|
||||
|
||||
@@ -7,7 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
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.readToList
|
||||
import org.signal.core.util.requireLong
|
||||
@@ -88,12 +88,10 @@ fun DistributionListTables.restoreFromBackup(dlist: BackupDistributionList, back
|
||||
|
||||
fun DistributionListTables.clearAllDataForBackupRestore() {
|
||||
writableDatabase
|
||||
.delete(DistributionListTables.ListTable.TABLE_NAME)
|
||||
.run()
|
||||
.deleteAll(DistributionListTables.ListTable.TABLE_NAME)
|
||||
|
||||
writableDatabase
|
||||
.delete(DistributionListTables.MembershipTable.TABLE_NAME)
|
||||
.run()
|
||||
.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME)
|
||||
}
|
||||
|
||||
private fun DistributionListPrivacyMode.toBackupPrivacyMode(): BackupDistributionList.PrivacyMode {
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.database.Cursor
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
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.nullIfBlank
|
||||
import org.signal.core.util.requireBoolean
|
||||
@@ -155,7 +155,7 @@ fun RecipientTable.restoreSelfFromBackup(accountData: AccountData, selfId: Recip
|
||||
}
|
||||
|
||||
fun RecipientTable.clearAllDataForBackupRestore() {
|
||||
writableDatabase.delete(RecipientTable.TABLE_NAME).run()
|
||||
writableDatabase.deleteAll(RecipientTable.TABLE_NAME)
|
||||
SqlUtil.resetAutoIncrementValue(writableDatabase, RecipientTable.TABLE_NAME)
|
||||
|
||||
RecipientId.clearCache()
|
||||
|
||||
@@ -56,7 +56,7 @@ object AccountDataProcessor {
|
||||
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context),
|
||||
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
|
||||
linkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
|
||||
notDiscoverableByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isUnlisted,
|
||||
notDiscoverableByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode.isUndiscoverable,
|
||||
phoneNumberSharingMode = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
|
||||
preferContactAvatars = SignalStore.settings().isPreferSystemContactPhotos,
|
||||
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
||||
@@ -86,7 +86,7 @@ object AccountDataProcessor {
|
||||
TextSecurePreferences.setTypingIndicatorsEnabled(context, settings.typingIndicators)
|
||||
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, settings.sealedSenderIndicators)
|
||||
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.settings().isPreferSystemContactPhotos = settings.preferContactAvatars
|
||||
SignalStore.settings().universalExpireTimer = settings.universalExpireTimer
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 androidx.appcompat.widget.AppCompatImageView
|
||||
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 org.thoughtcrime.securesms.R
|
||||
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.database.model.databaseprotos.GiftBadge
|
||||
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.util.ScreenDensity
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
@@ -43,35 +43,35 @@ class BadgeImageView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun setBadgeFromRecipient(recipient: Recipient?) {
|
||||
getGlideRequests()?.let {
|
||||
getGlideRequestManager()?.let {
|
||||
setBadgeFromRecipient(recipient, it)
|
||||
} ?: clearDrawable()
|
||||
}
|
||||
|
||||
fun setBadgeFromRecipient(recipient: Recipient?, glideRequests: GlideRequests) {
|
||||
fun setBadgeFromRecipient(recipient: Recipient?, requestManager: RequestManager) {
|
||||
if (recipient == null || recipient.badges.isEmpty()) {
|
||||
setBadge(null, glideRequests)
|
||||
setBadge(null, requestManager)
|
||||
} else if (recipient.isSelf) {
|
||||
val badge = recipient.featuredBadge
|
||||
if (badge == null || !badge.visible || badge.isExpired()) {
|
||||
setBadge(null, glideRequests)
|
||||
setBadge(null, requestManager)
|
||||
} else {
|
||||
setBadge(badge, glideRequests)
|
||||
setBadge(badge, requestManager)
|
||||
}
|
||||
} else {
|
||||
setBadge(recipient.featuredBadge, glideRequests)
|
||||
setBadge(recipient.featuredBadge, requestManager)
|
||||
}
|
||||
}
|
||||
|
||||
fun setBadge(badge: Badge?) {
|
||||
getGlideRequests()?.let {
|
||||
getGlideRequestManager()?.let {
|
||||
setBadge(badge, it)
|
||||
} ?: clearDrawable()
|
||||
}
|
||||
|
||||
fun setBadge(badge: Badge?, glideRequests: GlideRequests) {
|
||||
fun setBadge(badge: Badge?, requestManager: RequestManager) {
|
||||
if (badge != null) {
|
||||
glideRequests
|
||||
requestManager
|
||||
.load(badge)
|
||||
.downsample(DownsampleStrategy.NONE)
|
||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||
@@ -79,21 +79,21 @@ class BadgeImageView @JvmOverloads constructor(
|
||||
|
||||
isClickable = true
|
||||
} else {
|
||||
glideRequests
|
||||
requestManager
|
||||
.clear(this)
|
||||
clearDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
fun setGiftBadge(badge: GiftBadge?, glideRequests: GlideRequests) {
|
||||
fun setGiftBadge(badge: GiftBadge?, requestManager: RequestManager) {
|
||||
if (badge != null) {
|
||||
glideRequests
|
||||
requestManager
|
||||
.load(GiftBadgeModel(badge))
|
||||
.downsample(DownsampleStrategy.NONE)
|
||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), ScreenDensity.getBestDensityBucketForDevice(), ThemeUtil.isDarkTheme(context)))
|
||||
.into(this)
|
||||
} else {
|
||||
glideRequests
|
||||
requestManager
|
||||
.clear(this)
|
||||
clearDrawable()
|
||||
}
|
||||
@@ -106,9 +106,9 @@ class BadgeImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGlideRequests(): GlideRequests? {
|
||||
private fun getGlideRequestManager(): RequestManager? {
|
||||
return try {
|
||||
GlideApp.with(this)
|
||||
Glide.with(this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// View not attached to an activity or activity destroyed
|
||||
null
|
||||
|
||||
@@ -9,13 +9,13 @@ import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.use
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
import org.thoughtcrime.securesms.badges.gifts.Gifts.formatExpiry
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
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)
|
||||
actionView.icon = null
|
||||
actionView.setOnClickListener { callback.onViewGiftBadgeClicked() }
|
||||
@@ -88,7 +88,7 @@ class GiftMessageView @JvmOverloads constructor(
|
||||
)
|
||||
}
|
||||
|
||||
badgeView.setGiftBadge(giftBadge, glideRequests)
|
||||
badgeView.setGiftBadge(giftBadge, requestManager)
|
||||
}
|
||||
|
||||
fun onGiftNotOpened() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.Key
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
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.badges.glide.BadgeSpriteTransformation
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
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
|
||||
|
||||
GlideApp.with(badge)
|
||||
Glide.with(badge)
|
||||
.load(model.badge)
|
||||
.downsample(DownsampleStrategy.NONE)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
|
||||
@@ -2,10 +2,10 @@ package org.thoughtcrime.securesms.badges.models
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.bumptech.glide.Glide
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
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.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
@@ -49,7 +49,7 @@ object BadgeDisplay112 {
|
||||
|
||||
override fun bind(model: GiftModel) {
|
||||
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
|
||||
}
|
||||
|
||||
if (!url.startsWith(HTTPS_LINK_PREFIX) || !url.startsWith(SNGL_LINK_PREFIX)) {
|
||||
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
import org.thoughtcrime.securesms.databinding.CallLogAdapterItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.CallLogCreateCallLinkItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.ConversationListItemClearFilterBinding
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
@@ -272,7 +272,7 @@ class CallLogAdapter(
|
||||
}
|
||||
|
||||
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.callRecipientName.text = if (searchQuery != null) {
|
||||
SearchUtil.getHighlightedSpan(
|
||||
@@ -305,7 +305,7 @@ class CallLogAdapter(
|
||||
|
||||
val color = ContextCompat.getColor(
|
||||
context,
|
||||
if (call.record.event == CallTable.Event.MISSED) {
|
||||
if (call.record.event.isMissedCall()) {
|
||||
R.color.signal_colorError
|
||||
} else {
|
||||
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.GROUP_CALL_TYPE -> when {
|
||||
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.direction == CallTable.Direction.INCOMING -> R.drawable.symbol_arrow_downleft_compact_16
|
||||
call.direction == CallTable.Direction.OUTGOING -> R.drawable.symbol_arrow_upright_compact_16
|
||||
@@ -389,8 +389,8 @@ class CallLogAdapter(
|
||||
@StringRes
|
||||
private fun getCallStateStringRes(call: CallTable.Call): Int {
|
||||
return when (call.messageType) {
|
||||
MessageTypes.MISSED_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__missed
|
||||
MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__missed
|
||||
MessageTypes.MISSED_VIDEO_CALL_TYPE,
|
||||
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_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__incoming
|
||||
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__outgoing
|
||||
@@ -398,6 +398,7 @@ class CallLogAdapter(
|
||||
MessageTypes.GROUP_CALL_TYPE -> when {
|
||||
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_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.direction == CallTable.Direction.INCOMING -> R.string.CallLogAdapter__incoming
|
||||
call.direction == CallTable.Direction.OUTGOING -> R.string.CallLogAdapter__outgoing
|
||||
|
||||
@@ -16,9 +16,10 @@ import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
@@ -30,9 +31,9 @@ import java.util.List;
|
||||
public class AlbumThumbnailView extends FrameLayout {
|
||||
|
||||
private @Nullable SlideClickListener thumbnailClickListener;
|
||||
private @Nullable SlidesClickedListener downloadClickListener;
|
||||
private @Nullable SlidesClickedListener cancelDownloadClickListener;
|
||||
private @Nullable SlideClickListener playVideoClickListener;
|
||||
private @Nullable SlidesClickedListener startTransferClickListener;
|
||||
private @Nullable SlidesClickedListener cancelTransferClickListener;
|
||||
private @Nullable SlideClickListener playVideoClickListener;
|
||||
|
||||
private int currentSizeClass;
|
||||
|
||||
@@ -65,23 +66,23 @@ public class AlbumThumbnailView extends FrameLayout {
|
||||
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) {
|
||||
throw new IllegalStateException("Provided less than two slides.");
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
transferControlsStub.get().setShowSecondaryText(true);
|
||||
transferControlsStub.get().setDownloadClickListener(
|
||||
transferControlsStub.get().setTransferClickListener(
|
||||
v -> {
|
||||
if (downloadClickListener != null) {
|
||||
downloadClickListener.onClick(v, slides);
|
||||
if (startTransferClickListener != null) {
|
||||
startTransferClickListener.onClick(v, slides);
|
||||
}
|
||||
});
|
||||
transferControlsStub.get().setCancelClickListener(
|
||||
v -> {
|
||||
if (cancelDownloadClickListener != null) {
|
||||
cancelDownloadClickListener.onClick(v, slides);
|
||||
if (cancelTransferClickListener != null) {
|
||||
cancelTransferClickListener.onClick(v, slides);
|
||||
}
|
||||
});
|
||||
transferControlsStub.get().setSlides(slides);
|
||||
@@ -98,7 +99,7 @@ public class AlbumThumbnailView extends FrameLayout {
|
||||
currentSizeClass = sizeClass;
|
||||
}
|
||||
|
||||
showSlides(glideRequests, slides);
|
||||
showSlides(requestManager, slides);
|
||||
applyCorners();
|
||||
forceLayout();
|
||||
}
|
||||
@@ -117,12 +118,12 @@ public class AlbumThumbnailView extends FrameLayout {
|
||||
thumbnailClickListener = listener;
|
||||
}
|
||||
|
||||
public void setDownloadClickListener(SlidesClickedListener listener) {
|
||||
this.downloadClickListener = listener;
|
||||
public void setStartTransferClickListener(SlidesClickedListener listener) {
|
||||
this.startTransferClickListener = listener;
|
||||
}
|
||||
|
||||
public void setCancelDownloadClickListener(SlidesClickedListener listener) {
|
||||
this.cancelDownloadClickListener = listener;
|
||||
public void setCancelTransferClickListener(SlidesClickedListener listener) {
|
||||
this.cancelTransferClickListener = listener;
|
||||
}
|
||||
|
||||
public void setPlayVideoClickListener(SlideClickListener listener) {
|
||||
@@ -261,21 +262,21 @@ public class AlbumThumbnailView extends FrameLayout {
|
||||
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);
|
||||
setSlide(glideRequests, slides.get(0), R.id.album_cell_1, showControls);
|
||||
setSlide(glideRequests, slides.get(1), R.id.album_cell_2, showControls);
|
||||
setSlide(requestManager, slides.get(0), R.id.album_cell_1, showControls);
|
||||
setSlide(requestManager, slides.get(1), R.id.album_cell_2, showControls);
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
@@ -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);
|
||||
cell.showSecondaryText(false);
|
||||
cell.setThumbnailClickListener(defaultThumbnailClickListener);
|
||||
cell.setDownloadClickListener(downloadClickListener);
|
||||
cell.setCancelDownloadClickListener(cancelDownloadClickListener);
|
||||
cell.setStartTransferClickListener(startTransferClickListener);
|
||||
cell.setCancelTransferClickListener(cancelTransferClickListener);
|
||||
if (MediaUtil.isInstantVideoSupported(slide)) {
|
||||
cell.setPlayVideoClickListener(playVideoClickListener);
|
||||
}
|
||||
cell.setOnLongClickListener(defaultLongClickListener);
|
||||
cell.setImageResource(glideRequests, slide, showControls, false);
|
||||
cell.setImageResource(requestManager, slide, showControls, false);
|
||||
}
|
||||
|
||||
private int sizeClass(int size) {
|
||||
|
||||
@@ -15,6 +15,9 @@ import androidx.annotation.Px;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
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.MultiTransformation;
|
||||
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.dependencies.ApplicationDependencies;
|
||||
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.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
@@ -129,10 +129,10 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
*/
|
||||
public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) {
|
||||
if (recipient.isSelf()) {
|
||||
setAvatar(GlideApp.with(this), null, quickContactEnabled);
|
||||
setAvatar(Glide.with(this), null, quickContactEnabled);
|
||||
AvatarUtil.loadIconIntoImageView(recipient, this);
|
||||
} 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.
|
||||
*/
|
||||
public void setAvatar(@Nullable Recipient recipient) {
|
||||
setAvatar(GlideApp.with(this), recipient, false);
|
||||
setAvatar(Glide.with(this), recipient, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows self as the profile avatar.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
.withUseSelfProfileAvatar(useSelfProfileAvatar)
|
||||
.withQuickContactEnabled(quickContactEnabled)
|
||||
@@ -166,10 +166,10 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
}
|
||||
|
||||
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) {
|
||||
RecipientContactPhoto photo = (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) ? new RecipientContactPhoto(recipient,
|
||||
new ProfileContactPhoto(Recipient.self()))
|
||||
@@ -199,7 +199,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
transforms.add(new CircleCrop());
|
||||
blurred = shouldBlur;
|
||||
|
||||
GlideRequest<Drawable> request = requestManager.load(photo.contactPhoto)
|
||||
RequestBuilder<Drawable> request = requestManager.load(photo.contactPhoto)
|
||||
.dontAnimate()
|
||||
.fallback(fallbackContactPhotoDrawable)
|
||||
.error(fallbackContactPhotoDrawable)
|
||||
@@ -265,7 +265,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
.getPhotoForGroup()
|
||||
.asDrawable(getContext(), color);
|
||||
|
||||
GlideApp.with(this)
|
||||
Glide.with(this)
|
||||
.load(avatarBytes)
|
||||
.dontAnimate()
|
||||
.fallback(fallback)
|
||||
|
||||
@@ -9,11 +9,9 @@ import android.widget.ImageView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterInside;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
@@ -55,15 +53,15 @@ public class BorderlessImageView extends FrameLayout {
|
||||
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;
|
||||
|
||||
if (slide.hasSticker()) {
|
||||
image.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
image.setImageResource(glideRequests, slide, showControls, false);
|
||||
image.setImageResource(requestManager, slide, showControls, false);
|
||||
} else {
|
||||
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);
|
||||
@@ -74,6 +72,6 @@ public class BorderlessImageView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void setDownloadClickListener(@NonNull SlidesClickedListener listener) {
|
||||
image.setDownloadClickListener(listener);
|
||||
image.setStartTransferClickListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,23 +114,23 @@ public final class ContactFilterView extends FrameLayout {
|
||||
int defStyle)
|
||||
{
|
||||
final TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.ContactFilterToolbar,
|
||||
R.styleable.ContactFilterView,
|
||||
defStyle,
|
||||
0);
|
||||
|
||||
int styleResource = attributes.getResourceId(R.styleable.ContactFilterToolbar_searchTextStyle, -1);
|
||||
int styleResource = attributes.getResourceId(R.styleable.ContactFilterView_searchTextStyle, -1);
|
||||
if (styleResource != -1) {
|
||||
TextViewCompat.setTextAppearance(searchText, styleResource);
|
||||
}
|
||||
if (!attributes.getBoolean(R.styleable.ContactFilterToolbar_showDialpad, true)) {
|
||||
if (!attributes.getBoolean(R.styleable.ContactFilterView_showDialpad, true)) {
|
||||
dialpadToggle.setVisibility(GONE);
|
||||
}
|
||||
|
||||
if (attributes.getBoolean(R.styleable.ContactFilterToolbar_cfv_autoFocus, true)) {
|
||||
if (attributes.getBoolean(R.styleable.ContactFilterView_cfv_autoFocus, true)) {
|
||||
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) {
|
||||
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.v2.computed.FormattedDate;
|
||||
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.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
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) {
|
||||
dateView.forceLayout();
|
||||
if (messageRecord.isFailed()) {
|
||||
if (messageRecord.isMediaPending()) {
|
||||
dateView.setText(null);
|
||||
} else if (messageRecord.isFailed()) {
|
||||
int errorMsg;
|
||||
if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||
errorMsg = R.string.ConversationItem_error_network_not_delivered;
|
||||
|
||||
@@ -16,10 +16,10 @@ import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Px
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.core.os.bundleOf
|
||||
import com.bumptech.glide.RequestManager
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener
|
||||
@@ -192,7 +192,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
|
||||
@UiThread
|
||||
fun setImageResource(
|
||||
glideRequests: GlideRequests,
|
||||
requestManager: RequestManager,
|
||||
slides: List<Slide>,
|
||||
showControls: Boolean,
|
||||
isPreview: Boolean
|
||||
@@ -223,7 +223,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
|
||||
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
|
||||
} else {
|
||||
state = state.copy(
|
||||
@@ -232,7 +232,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
)
|
||||
|
||||
state.applyState(thumbnail, album)
|
||||
album.get().setSlides(glideRequests, slides, showControls)
|
||||
album.get().setSlides(requestManager, slides, showControls)
|
||||
touchDelegate = album.get().touchDelegate
|
||||
}
|
||||
}
|
||||
@@ -251,10 +251,10 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
state.applyState(thumbnail, album)
|
||||
}
|
||||
|
||||
fun setDownloadClickListener(listener: SlidesClickedListener?) {
|
||||
fun setStartTransferClickListener(listener: SlidesClickedListener?) {
|
||||
state = state.copy(
|
||||
thumbnailViewState = state.thumbnailViewState.copy(downloadClickListener = listener),
|
||||
albumViewState = state.albumViewState.copy(downloadClickListener = listener)
|
||||
thumbnailViewState = state.thumbnailViewState.copy(startTransferClickListener = listener),
|
||||
albumViewState = state.albumViewState.copy(startTransferClickListener = listener)
|
||||
)
|
||||
|
||||
state.applyState(thumbnail, album)
|
||||
@@ -269,10 +269,10 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
state.applyState(thumbnail, album)
|
||||
}
|
||||
|
||||
fun setCancelDownloadClickListener(listener: SlidesClickedListener?) {
|
||||
fun setCancelTransferClickListener(listener: SlidesClickedListener?) {
|
||||
state = state.copy(
|
||||
thumbnailViewState = state.thumbnailViewState.copy(cancelDownloadClickListener = listener),
|
||||
albumViewState = state.albumViewState.copy(cancelDownloadClickListener = listener)
|
||||
thumbnailViewState = state.thumbnailViewState.copy(cancelTransferClickListener = listener),
|
||||
albumViewState = state.albumViewState.copy(cancelTransferClickListener = listener)
|
||||
)
|
||||
|
||||
state.applyState(thumbnail, album)
|
||||
|
||||
@@ -34,9 +34,9 @@ data class ConversationItemThumbnailState(
|
||||
@IgnoredOnParcel
|
||||
private val clickListener: SlideClickListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val downloadClickListener: SlidesClickedListener? = null,
|
||||
private val startTransferClickListener: SlidesClickedListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val cancelDownloadClickListener: SlidesClickedListener? = null,
|
||||
private val cancelTransferClickListener: SlidesClickedListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val playVideoClickListener: SlideClickListener? = null,
|
||||
@IgnoredOnParcel
|
||||
@@ -63,8 +63,8 @@ data class ConversationItemThumbnailState(
|
||||
thumbnailView.get().isClickable = clickable
|
||||
thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||
thumbnailView.get().setThumbnailClickListener(clickListener)
|
||||
thumbnailView.get().setDownloadClickListener(downloadClickListener)
|
||||
thumbnailView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
|
||||
thumbnailView.get().setStartTransferClickListener(startTransferClickListener)
|
||||
thumbnailView.get().setCancelTransferClickListener(cancelTransferClickListener)
|
||||
thumbnailView.get().setPlayVideoClickListener(playVideoClickListener)
|
||||
thumbnailView.get().setOnLongClickListener(longClickListener)
|
||||
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
|
||||
@@ -78,9 +78,9 @@ data class ConversationItemThumbnailState(
|
||||
@IgnoredOnParcel
|
||||
private val clickListener: SlideClickListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val downloadClickListener: SlidesClickedListener? = null,
|
||||
private val startTransferClickListener: SlidesClickedListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val cancelDownloadClickListener: SlidesClickedListener? = null,
|
||||
private val cancelTransferClickListener: SlidesClickedListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val playVideoClickListener: SlideClickListener? = null,
|
||||
@IgnoredOnParcel
|
||||
@@ -103,8 +103,8 @@ data class ConversationItemThumbnailState(
|
||||
albumView.get().isClickable = clickable
|
||||
albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||
albumView.get().setThumbnailClickListener(clickListener)
|
||||
albumView.get().setDownloadClickListener(downloadClickListener)
|
||||
albumView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
|
||||
albumView.get().setStartTransferClickListener(startTransferClickListener)
|
||||
albumView.get().setCancelTransferClickListener(cancelTransferClickListener)
|
||||
albumView.get().setPlayVideoClickListener(playVideoClickListener)
|
||||
albumView.get().setOnLongClickListener(longClickListener)
|
||||
albumView.get().setCellBackgroundColor(cellBackgroundColor)
|
||||
|
||||
@@ -11,9 +11,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.List;
|
||||
@@ -49,7 +50,7 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
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()) {
|
||||
indicator.stopAnimation();
|
||||
return;
|
||||
@@ -64,7 +65,7 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
typistCount.setVisibility(GONE);
|
||||
|
||||
if (isGroupThread) {
|
||||
presentGroupThreadAvatars(glideRequests, typists);
|
||||
presentGroupThreadAvatars(requestManager, typists);
|
||||
}
|
||||
|
||||
if (hasWallpaper) {
|
||||
@@ -84,23 +85,23 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
return indicator.isActive();
|
||||
}
|
||||
|
||||
private void presentGroupThreadAvatars(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists) {
|
||||
avatar1.setAvatar(glideRequests, typists.get(0), typists.size() == 1);
|
||||
private void presentGroupThreadAvatars(@NonNull RequestManager requestManager, @NonNull List<Recipient> typists) {
|
||||
avatar1.setAvatar(requestManager, typists.get(0), typists.size() == 1);
|
||||
avatar1.setVisibility(VISIBLE);
|
||||
badge1.setBadgeFromRecipient(typists.get(0), glideRequests);
|
||||
badge1.setBadgeFromRecipient(typists.get(0), requestManager);
|
||||
badge1.setVisibility(VISIBLE);
|
||||
|
||||
if (typists.size() > 1) {
|
||||
avatar2.setAvatar(glideRequests, typists.get(1), false);
|
||||
avatar2.setAvatar(requestManager, typists.get(1), false);
|
||||
avatar2.setVisibility(VISIBLE);
|
||||
badge2.setBadgeFromRecipient(typists.get(1), glideRequests);
|
||||
badge2.setBadgeFromRecipient(typists.get(1), requestManager);
|
||||
badge2.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (typists.size() == 3) {
|
||||
avatar3.setAvatar(glideRequests, typists.get(2), false);
|
||||
avatar3.setAvatar(requestManager, typists.get(2), false);
|
||||
avatar3.setVisibility(VISIBLE);
|
||||
badge3.setBadgeFromRecipient(typists.get(2), glideRequests);
|
||||
badge3.setBadgeFromRecipient(typists.get(2), requestManager);
|
||||
badge3.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
@@ -146,7 +147,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
||||
setVisibility(View.VISIBLE);
|
||||
ViewUtil.setPaddingStart(this, 0);
|
||||
ViewUtil.setPaddingEnd(this, horizontalPadding);
|
||||
setImageResource(R.drawable.ic_delivery_status_sending);
|
||||
setImageResource(R.drawable.symbol_messagestatus_sending_24);
|
||||
updateContentDescription();
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||
ViewUtil.setPaddingEnd(this, 0);
|
||||
clearAnimation();
|
||||
setImageResource(R.drawable.ic_delivery_status_sent);
|
||||
setImageResource(R.drawable.symbol_messagestatus_sent_24);
|
||||
updateContentDescription();
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ public class DeliveryStatusView extends AppCompatImageView {
|
||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||
ViewUtil.setPaddingEnd(this, 0);
|
||||
clearAnimation();
|
||||
setImageResource(R.drawable.ic_delivery_status_delivered);
|
||||
setImageResource(R.drawable.symbol_messagestatus_delivered_24);
|
||||
updateContentDescription();
|
||||
}
|
||||
|
||||
@@ -176,12 +177,12 @@ public class DeliveryStatusView extends AppCompatImageView {
|
||||
ViewUtil.setPaddingStart(this, horizontalPadding);
|
||||
ViewUtil.setPaddingEnd(this, 0);
|
||||
clearAnimation();
|
||||
setImageResource(R.drawable.ic_delivery_status_read);
|
||||
setImageResource(R.drawable.symbol_messagestatus_read_24);
|
||||
updateContentDescription();
|
||||
}
|
||||
|
||||
public void setTint(int color) {
|
||||
setColorFilter(color);
|
||||
setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
private void updateContentDescription() {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.Nullable;
|
||||
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.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ContextUtil;
|
||||
import org.thoughtcrime.securesms.util.DrawableUtil;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class FromTextView extends SimpleEmojiTextView {
|
||||
|
||||
private static final String TAG = Log.tag(FromTextView.class);
|
||||
@@ -71,17 +69,23 @@ public class FromTextView extends SimpleEmojiTextView {
|
||||
|
||||
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 setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private Drawable getBlocked() {
|
||||
return getDrawable(R.drawable.symbol_block_16);
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
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.LinkPreviewRepository;
|
||||
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.Slide;
|
||||
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.setAdapter(stickerSuggestionAdapter);
|
||||
@@ -212,14 +212,14 @@ public class InputPanel extends ConstraintLayout
|
||||
composeText.setMediaListener(listener);
|
||||
}
|
||||
|
||||
public void setQuote(@NonNull GlideRequests glideRequests,
|
||||
public void setQuote(@NonNull RequestManager requestManager,
|
||||
long id,
|
||||
@NonNull Recipient author,
|
||||
@Nullable CharSequence body,
|
||||
@NonNull SlideDeck attachments,
|
||||
@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()
|
||||
: 0;
|
||||
@@ -325,10 +325,10 @@ public class InputPanel extends ConstraintLayout
|
||||
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()) {
|
||||
this.linkPreview.setVisibility(View.VISIBLE);
|
||||
this.linkPreview.setLinkPreview(glideRequests, preview.get(), true);
|
||||
this.linkPreview.setLinkPreview(requestManager, preview.get(), true);
|
||||
} else {
|
||||
this.linkPreview.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public class InputPanel extends ConstraintLayout
|
||||
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());
|
||||
if (!fromDraft) {
|
||||
MessageStyler.convertSpoilersToComposeMode(textToEdit);
|
||||
@@ -415,14 +415,14 @@ public class InputPanel extends ConstraintLayout
|
||||
if (quote == null) {
|
||||
clearQuote();
|
||||
} 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();
|
||||
updateEditModeThumbnail(glideRequests);
|
||||
updateEditModeThumbnail(requestManager);
|
||||
updateEditModeUi();
|
||||
}
|
||||
|
||||
private void updateEditModeThumbnail(@NonNull GlideRequests glideRequests) {
|
||||
private void updateEditModeThumbnail(@NonNull RequestManager requestManager) {
|
||||
if (messageToEdit instanceof MmsMessageRecord) {
|
||||
MmsMessageRecord mediaEditMessage = (MmsMessageRecord) messageToEdit;
|
||||
SlideDeck slideDeck = mediaEditMessage.getSlideDeck();
|
||||
@@ -430,7 +430,7 @@ public class InputPanel extends ConstraintLayout
|
||||
|
||||
if (imageVideoSlide != null && imageVideoSlide.getUri() != null) {
|
||||
editMessageThumbnail.setVisibility(VISIBLE);
|
||||
glideRequests.load(new DecryptableStreamUriLoader.DecryptableUri(imageVideoSlide.getUri()))
|
||||
requestManager.load(new DecryptableStreamUriLoader.DecryptableUri(imageVideoSlide.getUri()))
|
||||
.centerCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.into(editMessageThumbnail);
|
||||
|
||||
@@ -16,13 +16,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.signal.ringrtc.CallLinkRootKey;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -162,11 +163,11 @@ public class LinkPreviewView extends FrameLayout {
|
||||
noPreview.setText(getLinkPreviewErrorString(customError));
|
||||
}
|
||||
|
||||
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
||||
setLinkPreview(glideRequests, linkPreview, showThumbnail, true, false);
|
||||
public void setLinkPreview(@NonNull RequestManager requestManager, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
||||
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);
|
||||
noPreview.setVisibility(GONE);
|
||||
|
||||
@@ -216,13 +217,13 @@ public class LinkPreviewView extends FrameLayout {
|
||||
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
|
||||
thumbnail.setVisibility(VISIBLE);
|
||||
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);
|
||||
} else if (callLinkRootKey != null) {
|
||||
thumbnail.setVisibility(VISIBLE);
|
||||
thumbnailState.applyState(thumbnail);
|
||||
thumbnail.get().setImageDrawable(
|
||||
glideRequests,
|
||||
requestManager,
|
||||
Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER
|
||||
.getPhotoForCallLink()
|
||||
.asDrawable(getContext(),
|
||||
|
||||
@@ -22,7 +22,7 @@ data class LinkPreviewViewThumbnailState(
|
||||
fun applyState(thumbnail: Stub<OutlinedThumbnailView>) {
|
||||
if (thumbnail.resolved()) {
|
||||
thumbnail.get().setCorners(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||
thumbnail.get().setDownloadClickListener(downloadListener)
|
||||
thumbnail.get().setStartTransferClickListener(downloadListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,19 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.PromptBatterySaverBottomSheetBinding
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.LocalMetrics
|
||||
import org.thoughtcrime.securesms.util.PowerManagerCompat
|
||||
|
||||
@RequiresApi(23)
|
||||
class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(PromptBatterySaverDialogFragment::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
@@ -51,8 +54,11 @@ class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFrag
|
||||
|
||||
binding.continueButton.setOnClickListener {
|
||||
PowerManagerCompat.requestIgnoreBatteryOptimizations(requireContext())
|
||||
Log.i(TAG, "Requested to ignore battery optimizations, clearing local metrics.")
|
||||
LocalMetrics.clear()
|
||||
}
|
||||
binding.dismissButton.setOnClickListener {
|
||||
Log.i(TAG, "User denied request to ignore battery optimizations.")
|
||||
SignalStore.uiHints().markDismissedBatterySaverPrompt()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
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.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
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,
|
||||
@NonNull Recipient author,
|
||||
@Nullable CharSequence body,
|
||||
@@ -213,7 +213,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
||||
this.author.observeForever(this);
|
||||
setQuoteAuthor(author);
|
||||
setQuoteText(resolveBody(body, quoteType), attachments, originalMissing, storyReaction);
|
||||
setQuoteAttachment(glideRequests, body, attachments, originalMissing);
|
||||
setQuoteAttachment(requestManager, body, attachments, originalMissing);
|
||||
setQuoteMissingFooter(originalMissing);
|
||||
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 preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW;
|
||||
|
||||
@@ -359,7 +359,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
||||
attachmentVideoOVerlayStub.setVisibility(GONE);
|
||||
attachmentNameViewStub.setVisibility(GONE);
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
glideRequests.load(model)
|
||||
requestManager.load(model)
|
||||
.centerCrop()
|
||||
.override(thumbWidth, thumbHeight)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
@@ -377,7 +377,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
||||
attachmentVideoOVerlayStub.setVisibility(GONE);
|
||||
attachmentNameViewStub.setVisibility(GONE);
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
glideRequests.load(R.drawable.ic_gift_thumbnail)
|
||||
requestManager.load(R.drawable.ic_gift_thumbnail)
|
||||
.centerCrop()
|
||||
.override(thumbWidth, thumbHeight)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
@@ -404,7 +404,7 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
|
||||
if (imageVideoSlide.hasVideo() && !imageVideoSlide.isVideoGif()) {
|
||||
attachmentVideoOVerlayStub.setVisibility(VISIBLE);
|
||||
}
|
||||
glideRequests.load(new DecryptableUri(imageVideoSlide.getUri()))
|
||||
requestManager.load(new DecryptableUri(imageVideoSlide.getUri()))
|
||||
.centerCrop()
|
||||
.override(thumbWidth, thumbHeight)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
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.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
||||
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);
|
||||
|
||||
GlideApp.with(getContext().getApplicationContext())
|
||||
Glide.with(getContext().getApplicationContext())
|
||||
.load(uri)
|
||||
.signature(signature)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
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.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
@@ -45,13 +45,13 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
||||
private TextView actionButtonView;
|
||||
private ConversationItemFooter footer;
|
||||
|
||||
private Contact contact;
|
||||
private Locale locale;
|
||||
private GlideRequests glideRequests;
|
||||
private EventListener eventListener;
|
||||
private CornerMask cornerMask;
|
||||
private int bigCornerRadius;
|
||||
private int smallCornerRadius;
|
||||
private Contact contact;
|
||||
private Locale locale;
|
||||
private RequestManager requestManager;
|
||||
private EventListener eventListener;
|
||||
private CornerMask cornerMask;
|
||||
private int bigCornerRadius;
|
||||
private int smallCornerRadius;
|
||||
|
||||
private final Map<RecipientId, LiveRecipient> activeRecipients = new HashMap<>();
|
||||
|
||||
@@ -111,10 +111,10 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
||||
cornerMask.mask(canvas);
|
||||
}
|
||||
|
||||
public void setContact(@NonNull Contact contact, @NonNull GlideRequests glideRequests, @NonNull Locale locale) {
|
||||
this.glideRequests = glideRequests;
|
||||
this.locale = locale;
|
||||
this.contact = contact;
|
||||
public void setContact(@NonNull Contact contact, @NonNull RequestManager requestManager, @NonNull Locale locale) {
|
||||
this.requestManager = requestManager;
|
||||
this.locale = locale;
|
||||
this.contact = contact;
|
||||
|
||||
Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeForeverObserver(this));
|
||||
this.activeRecipients.clear();
|
||||
@@ -172,17 +172,17 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
||||
|
||||
private void presentAvatar(@Nullable Uri uri) {
|
||||
if (uri != null) {
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.fallback(R.drawable.ic_contact_picture)
|
||||
.circleCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.dontAnimate()
|
||||
.into(avatarView);
|
||||
requestManager.load(new DecryptableUri(uri))
|
||||
.fallback(R.drawable.symbol_person_display_40)
|
||||
.circleCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.dontAnimate()
|
||||
.into(avatarView);
|
||||
} else {
|
||||
glideRequests.load(R.drawable.ic_contact_picture)
|
||||
.circleCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.into(avatarView);
|
||||
requestManager.load(R.drawable.symbol_person_display_40)
|
||||
.circleCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.into(avatarView);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -15,11 +14,12 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
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) {
|
||||
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), glideRequests, mediaRecords, this.listener));
|
||||
public void setMediaRecords(@NonNull RequestManager requestManager, @NonNull List<MediaTable.MediaRecord> mediaRecords) {
|
||||
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), requestManager, mediaRecords, this.listener));
|
||||
}
|
||||
|
||||
private static class ThreadPhotoRailAdapter extends RecyclerView.Adapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
|
||||
@@ -67,18 +67,18 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(ThreadPhotoRailAdapter.class);
|
||||
|
||||
@NonNull private final GlideRequests glideRequests;
|
||||
@NonNull private final RequestManager requestManager;
|
||||
|
||||
@Nullable private OnItemClickedListener clickedListener;
|
||||
|
||||
private final List<MediaTable.MediaRecord> mediaRecords = new ArrayList<>();
|
||||
|
||||
private ThreadPhotoRailAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull RequestManager requestManager,
|
||||
@NonNull List<MediaTable.MediaRecord> mediaRecords,
|
||||
@Nullable OnItemClickedListener listener)
|
||||
{
|
||||
this.glideRequests = glideRequests;
|
||||
this.requestManager = requestManager;
|
||||
this.clickedListener = listener;
|
||||
|
||||
this.mediaRecords.clear();
|
||||
@@ -103,7 +103,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
MediaTable.MediaRecord mediaRecord = mediaRecords.get(position);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(mediaRecord.getAttachment());
|
||||
|
||||
viewHolder.imageView.setImageResource(glideRequests, slide, false, false);
|
||||
viewHolder.imageView.setImageResource(requestManager, slide, false, false);
|
||||
viewHolder.imageView.setOnClickListener(v -> {
|
||||
MediaPreviewCache.INSTANCE.setDrawable(viewHolder.imageView.getImageDrawable());
|
||||
if (clickedListener != null) clickedListener.onItemClicked(viewHolder.imageView, mediaRecord);
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.Request;
|
||||
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.database.AttachmentTable;
|
||||
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.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
@@ -86,12 +85,12 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private final CornerMask cornerMask;
|
||||
|
||||
private final Stub<TransferControlView> transferControlViewStub;
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener downloadClickListener = null;
|
||||
private SlidesClickedListener cancelDownloadClickListener = null;
|
||||
private SlideClickListener playVideoClickListener = null;
|
||||
private Slide slide = null;
|
||||
private final Stub<TransferControlView> transferControlViewStub;
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener startTransferClickListener = null;
|
||||
private SlidesClickedListener cancelTransferClickListener = null;
|
||||
private SlideClickListener playVideoClickListener = null;
|
||||
private Slide slide = null;
|
||||
|
||||
|
||||
public ThumbnailView(Context context) {
|
||||
@@ -314,23 +313,23 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageDrawable(@NonNull GlideRequests glideRequests, @Nullable Drawable drawable) {
|
||||
glideRequests.clear(image);
|
||||
glideRequests.clear(blurHash);
|
||||
public void setImageDrawable(@NonNull RequestManager requestManager, @Nullable Drawable drawable) {
|
||||
requestManager.clear(image);
|
||||
requestManager.clear(blurHash);
|
||||
|
||||
image.setImageDrawable(drawable);
|
||||
blurHash.setImageDrawable(null);
|
||||
}
|
||||
|
||||
@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)
|
||||
{
|
||||
return setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
|
||||
return setImageResource(requestManager, slide, showControls, isPreview, 0, 0);
|
||||
}
|
||||
|
||||
@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,
|
||||
int naturalWidth, int naturalHeight)
|
||||
{
|
||||
@@ -340,10 +339,10 @@ public class ThumbnailView extends FrameLayout {
|
||||
transferControlViewStub.setVisibility(View.GONE);
|
||||
playOverlay.setVisibility(View.GONE);
|
||||
|
||||
glideRequests.clear(blurHash);
|
||||
requestManager.clear(blurHash);
|
||||
blurHash.setImageDrawable(null);
|
||||
|
||||
glideRequests.clear(image);
|
||||
requestManager.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
|
||||
int errorImageResource;
|
||||
@@ -363,7 +362,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
transferControlViewStub.get().setDownloadClickListener(new DownloadClickDispatcher());
|
||||
transferControlViewStub.get().setTransferClickListener(new DownloadClickDispatcher());
|
||||
transferControlViewStub.get().setCancelClickListener(new CancelClickDispatcher());
|
||||
if (MediaUtil.isInstantVideoSupported(slide)) {
|
||||
transferControlViewStub.get().setInstantPlaybackClickListener(new InstantVideoClickDispatcher());
|
||||
@@ -397,7 +396,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
Log.i(TAG, "loading part with id " + slide.asAttachment().getUri()
|
||||
+ ", progress " + slide.getTransferState() + ", fast preflight id: " +
|
||||
slide.asAttachment().getFastPreflightId());
|
||||
slide.asAttachment().fastPreflightId);
|
||||
|
||||
BlurHash previousBlurHash = this.slide != null ? this.slide.getPlaceholderBlur() : null;
|
||||
|
||||
@@ -414,10 +413,10 @@ public class ThumbnailView extends FrameLayout {
|
||||
boolean resultHandled = false;
|
||||
|
||||
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;
|
||||
} else if (!slide.hasPlaceholder()) {
|
||||
glideRequests.clear(blurHash);
|
||||
requestManager.clear(blurHash);
|
||||
blurHash.setImageDrawable(null);
|
||||
}
|
||||
|
||||
@@ -425,14 +424,14 @@ public class ThumbnailView extends FrameLayout {
|
||||
if (!MediaUtil.isJpegType(slide.getContentType()) && !MediaUtil.isVideoType(slide.getContentType())) {
|
||||
SettableFuture<Boolean> thumbnailFuture = new SettableFuture<>();
|
||||
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;
|
||||
} else {
|
||||
glideRequests.clear(image);
|
||||
requestManager.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
}
|
||||
|
||||
@@ -443,20 +442,20 @@ public class ThumbnailView extends FrameLayout {
|
||||
return result;
|
||||
}
|
||||
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
return setImageResource(glideRequests, uri, 0, 0);
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Uri uri) {
|
||||
return setImageResource(requestManager, uri, 0, 0);
|
||||
}
|
||||
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height) {
|
||||
return setImageResource(glideRequests, uri, width, height, true, null);
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull RequestManager requestManager, @NonNull Uri uri, int width, int height) {
|
||||
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<>();
|
||||
|
||||
transferControlViewStub.setVisibility(View.GONE);
|
||||
|
||||
GlideRequest<Drawable> request = glideRequests.load(new DecryptableUri(uri))
|
||||
RequestBuilder<Drawable> request = requestManager.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||
.listener(listener);
|
||||
@@ -483,12 +482,12 @@ public class ThumbnailView extends FrameLayout {
|
||||
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<>();
|
||||
|
||||
transferControlViewStub.setVisibility(View.GONE);
|
||||
|
||||
GlideRequest<Drawable> request = glideRequests.load(model)
|
||||
RequestBuilder<Drawable> request = requestManager.load(model)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.placeholder(model.getPlaceholder())
|
||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||
@@ -502,7 +501,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
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) {
|
||||
Log.d(TAG, "override: apply w" + width + "xh" + height);
|
||||
return request.override(width, height);
|
||||
@@ -516,12 +515,12 @@ public class ThumbnailView extends FrameLayout {
|
||||
this.thumbnailClickListener = listener;
|
||||
}
|
||||
|
||||
public void setDownloadClickListener(SlidesClickedListener listener) {
|
||||
this.downloadClickListener = listener;
|
||||
public void setStartTransferClickListener(SlidesClickedListener listener) {
|
||||
this.startTransferClickListener = listener;
|
||||
}
|
||||
|
||||
public void setCancelDownloadClickListener(SlidesClickedListener listener) {
|
||||
this.cancelDownloadClickListener = listener;
|
||||
public void setCancelTransferClickListener(SlidesClickedListener listener) {
|
||||
this.cancelTransferClickListener = listener;
|
||||
}
|
||||
|
||||
public void setPlayVideoClickListener(SlideClickListener listener) {
|
||||
@@ -532,8 +531,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
if (Util.equals(slide, other)) {
|
||||
|
||||
if (slide != null && other != null) {
|
||||
byte[] digestLeft = slide.asAttachment().getDigest();
|
||||
byte[] digestRight = other.asAttachment().getDigest();
|
||||
byte[] digestLeft = slide.asAttachment().remoteDigest;
|
||||
byte[] digestRight = other.asAttachment().remoteDigest;
|
||||
|
||||
return Arrays.equals(digestLeft, digestRight);
|
||||
}
|
||||
@@ -542,8 +541,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
private GlideRequest<Drawable> buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
GlideRequest<Drawable> request = applySizing(glideRequests.load(new DecryptableUri(Objects.requireNonNull(slide.getUri())))
|
||||
private RequestBuilder<Drawable> buildThumbnailRequestBuilder(@NonNull RequestManager requestManager, @NonNull Slide slide) {
|
||||
RequestBuilder<Drawable> requestBuilder = applySizing(requestManager.load(new DecryptableUri(Objects.requireNonNull(slide.getUri())))
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE)
|
||||
.transition(withCrossFade()));
|
||||
@@ -551,21 +550,21 @@ public class ThumbnailView extends FrameLayout {
|
||||
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
||||
|
||||
if (slide.isInProgress() || doNotShowMissingThumbnailImage) {
|
||||
return request;
|
||||
return requestBuilder;
|
||||
} 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) {
|
||||
glideRequests.clear(image);
|
||||
public void clear(RequestManager requestManager) {
|
||||
requestManager.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
|
||||
if (transferControlViewStub.resolved()) {
|
||||
transferControlViewStub.get().clear();
|
||||
}
|
||||
|
||||
glideRequests.clear(blurHash);
|
||||
requestManager.clear(blurHash);
|
||||
blurHash.setImageDrawable(null);
|
||||
|
||||
slide = null;
|
||||
@@ -594,9 +593,9 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
|
||||
private RequestBuilder<Bitmap> buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
GlideRequest<Bitmap> bitmap = glideRequests.asBitmap();
|
||||
BlurHash placeholderBlur = slide.getPlaceholderBlur();
|
||||
private RequestBuilder<Bitmap> buildPlaceholderRequestBuilder(@NonNull RequestManager requestManager, @NonNull Slide slide) {
|
||||
RequestBuilder<Bitmap> bitmap = requestManager.asBitmap();
|
||||
BlurHash placeholderBlur = slide.getPlaceholderBlur();
|
||||
|
||||
if (placeholderBlur != null) {
|
||||
bitmap = bitmap.load(placeholderBlur);
|
||||
@@ -604,7 +603,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
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) {
|
||||
return resizedRequest.centerCrop();
|
||||
} 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];
|
||||
fillTargetDimensions(size, dimens, bounds);
|
||||
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
||||
@@ -667,10 +666,10 @@ public class ThumbnailView extends FrameLayout {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.i(TAG, "onClick() for download button");
|
||||
if (downloadClickListener != null && slide != null) {
|
||||
downloadClickListener.onClick(view, Collections.singletonList(slide));
|
||||
if (startTransferClickListener != null && slide != null) {
|
||||
startTransferClickListener.onClick(view, Collections.singletonList(slide));
|
||||
} 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
|
||||
public void onClick(View view) {
|
||||
Log.i(TAG, "onClick() for cancel button");
|
||||
if (cancelDownloadClickListener != null && slide != null) {
|
||||
cancelDownloadClickListener.onClick(view, Collections.singletonList(slide));
|
||||
if (cancelTransferClickListener != null && slide != null) {
|
||||
cancelTransferClickListener.onClick(view, Collections.singletonList(slide));
|
||||
} 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 final GlideRequests glideRequests;
|
||||
private final ImageView blurHash;
|
||||
private final RequestManager requestManager;
|
||||
private final ImageView blurHash;
|
||||
|
||||
private BlurHashClearListener(@NonNull GlideRequests glideRequests, @NonNull ImageView blurHash) {
|
||||
this.glideRequests = glideRequests;
|
||||
this.blurHash = blurHash;
|
||||
private BlurHashClearListener(@NonNull RequestManager requestManager, @NonNull ImageView blurHash) {
|
||||
this.requestManager = requestManager;
|
||||
this.blurHash = blurHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
glideRequests.clear(blurHash);
|
||||
requestManager.clear(blurHash);
|
||||
blurHash.setImageDrawable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
glideRequests.clear(blurHash);
|
||||
requestManager.clear(blurHash);
|
||||
blurHash.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ import androidx.annotation.Px;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.shape.MaterialShapeDrawable;
|
||||
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.signal.core.util.logging.Log;
|
||||
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
|
||||
@@ -101,7 +101,7 @@ public class TooltipPopup extends PopupWindow {
|
||||
if (iconGlideModel != null) {
|
||||
ImageView iconView = getContentView().findViewById(R.id.tooltip_icon);
|
||||
iconView.setVisibility(View.VISIBLE);
|
||||
GlideApp.with(anchor.getContext()).load(iconGlideModel).into(iconView);
|
||||
Glide.with(anchor.getContext()).load(iconGlideModel).into(iconView);
|
||||
}
|
||||
|
||||
setElevation(10);
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
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.AttachmentRegionDecoder;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.ActionRequestListener;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
@@ -81,7 +81,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
}
|
||||
|
||||
@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 int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
||||
@@ -103,7 +103,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
|
||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||
Log.i(TAG, "Loading in standard image view...");
|
||||
setImageViewUri(glideRequests, uri, onMediaReady);
|
||||
setImageViewUri(requestManager, uri, onMediaReady);
|
||||
} else {
|
||||
Log.i(TAG, "Loading in subsampling image view...");
|
||||
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);
|
||||
subsamplingImageView.setVisibility(View.GONE);
|
||||
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
requestManager.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontTransform()
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
|
||||
@@ -34,6 +34,7 @@ public class EmojiImageView extends AppCompatImageView {
|
||||
setImageResource(R.drawable.ic_emoji);
|
||||
} else {
|
||||
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.widget.TextViewCompat;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
@@ -73,6 +74,7 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
private boolean isJumbomoji;
|
||||
private boolean forceJumboEmoji;
|
||||
private boolean renderSpoilers;
|
||||
private boolean shrinkWrap;
|
||||
|
||||
private MentionRendererDelegate mentionRendererDelegate;
|
||||
private SpoilerRendererDelegate spoilerRendererDelegate;
|
||||
@@ -96,6 +98,7 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
measureLastLine = a.getBoolean(R.styleable.EmojiTextView_measureLastLine, false);
|
||||
forceJumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
|
||||
renderSpoilers = a.getBoolean(R.styleable.EmojiTextView_emoji_renderSpoilers, false);
|
||||
shrinkWrap = a.getBoolean(R.styleable.EmojiTextView_emoji_shrinkWrap, false);
|
||||
a.recycle();
|
||||
|
||||
a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.textSize });
|
||||
@@ -224,6 +227,25 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
||||
|
||||
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();
|
||||
if (getLayout() == null || !measureLastLine || text == null || text.length() == 0) {
|
||||
lastLineWidth = -1;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
||||
this.resendListener = resendListener;
|
||||
|
||||
setTitle(R.string.UntrustedSendDialog_send_message);
|
||||
setIcon(R.drawable.ic_warning);
|
||||
setIcon(R.drawable.symbol_error_triangle_fill_24);
|
||||
setMessage(message);
|
||||
setPositiveButton(R.string.UntrustedSendDialog_send, this);
|
||||
setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
@@ -31,7 +31,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI
|
||||
this.resendListener = resendListener;
|
||||
|
||||
setTitle(R.string.UnverifiedSendDialog_send_message);
|
||||
setIcon(R.drawable.ic_warning);
|
||||
setIcon(R.drawable.symbol_error_triangle_fill_24);
|
||||
setMessage(message);
|
||||
setPositiveButton(R.string.UnverifiedSendDialog_send, this);
|
||||
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