Compare commits
478 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4bc2162f2 | |||
| bfd966217f | |||
| 797c02e893 | |||
| 65372e547a | |||
| 2454b2e0db | |||
| 0505a46603 | |||
| 7f77cd6a22 | |||
| efe7b3099f | |||
| 26a831b49f | |||
| a3a5bb8177 | |||
| 4282f3eb6d | |||
| 8a49db650a | |||
| fadd4ac61e | |||
| d0c14895d0 | |||
| 32ee18240b | |||
| cd10aa90cc | |||
| 33d28c4359 | |||
| 530403ec04 | |||
| f15072bc8d | |||
| 8c2db972cf | |||
| ff8f9ca81a | |||
| 40991cc8e9 | |||
| 2f551ee3f2 | |||
| f1ab0a05f1 | |||
| fb919466de | |||
| 4a4cf08cd8 | |||
| ed20c24326 | |||
| e01cbcec62 | |||
| 04d6ccc30e | |||
| 6860f96973 | |||
| 944c8530d8 | |||
| 8b1552952c | |||
| dfcadde076 | |||
| 55acd0f048 | |||
| 820c016aad | |||
| d1d63d83dc | |||
| 7da5b2cdef | |||
| 442dde5c40 | |||
| f038e81ff3 | |||
| 32c4fcb065 | |||
| e2703b459f | |||
| 405d99fbe2 | |||
| 7b89687206 | |||
| d74f1a386c | |||
| b041ed1510 | |||
| 3426556a51 | |||
| e2cb535f3f | |||
| 3b17a41415 | |||
| 631720f111 | |||
| ab031d3dad | |||
| 6101048f07 | |||
| 115f7063d5 | |||
| 159d67ec59 | |||
| e09ce4c820 | |||
| 8cfc013960 | |||
| a436c46cb2 | |||
| 893be51810 | |||
| 97b5a49e36 | |||
| 043f06e188 | |||
| fa13b464f8 | |||
| bfaaf20fd9 | |||
| 2f97b80b9c | |||
| eee9c967fa | |||
| 515981c044 | |||
| a06528e5e1 | |||
| 98194c854a | |||
| 2d60a88a75 | |||
| c3e7d6c74c | |||
| 8da66bc789 | |||
| 9ceb5b2e85 | |||
| 17b8e086c9 | |||
| 9a097d113d | |||
| 46ca1e16bb | |||
| d4d3124a90 | |||
| 35a9fddbb2 | |||
| 41e417ff0b | |||
| f6614c1174 | |||
| 9136bcf5e8 | |||
| 7c156d10d6 | |||
| 3372d942ec | |||
| 7fc9876b1e | |||
| cff62e9528 | |||
| 24f59b0a17 | |||
| 0a07800eba | |||
| c863e9ed4d | |||
| 523537cf05 | |||
| fd4543ffe0 | |||
| 83b0309f23 | |||
| 5cabe5ecfa | |||
| fae3004512 | |||
| e143c47c25 | |||
| 27c3fca324 | |||
| 26d637cafc | |||
| 03e8fe9f27 | |||
| 23939aeee3 | |||
| d7b793ce4c | |||
| d3096c56cb | |||
| b0e7b49056 | |||
| 2f0f26c328 | |||
| 7f2f5a182f | |||
| 33b88796e8 | |||
| 31e4db2186 | |||
| 76ad7866ec | |||
| e9804eccbb | |||
| d62d0efb1d | |||
| 3ec9cd1244 | |||
| bd5f48f193 | |||
| 98fc3e5b0b | |||
| d06c633dc4 | |||
| d401386e2d | |||
| 8f0f9e64b9 | |||
| bd5ac85ac0 | |||
| 417070e957 | |||
| a92638e897 | |||
| 08abe890ff | |||
| 66b6420f21 | |||
| b21bd5a01e | |||
| d11e8ec04b | |||
| 3e5fe0f1cb | |||
| b65d62e065 | |||
| fc55be0916 | |||
| a87aa0fbe2 | |||
| a44a105cbc | |||
| c4817ac017 | |||
| 65835606cc | |||
| ff26922afb | |||
| a894ba7a51 | |||
| cb63fe600c | |||
| 2d6146351d | |||
| e5953b25e1 | |||
| 6354cb194c | |||
| 8d6beb92cb | |||
| bb5edccf34 | |||
| 6d86b25acd | |||
| 04677d21bb | |||
| 20022b88fc | |||
| 3088d7f182 | |||
| ce8dafd33d | |||
| 6054285ddb | |||
| dabea5169b | |||
| 7fb5ceeda4 | |||
| dc6fd8be7f | |||
| c271b9c2de | |||
| 6fb6092a6b | |||
| 46bb64ad24 | |||
| bcd16ce296 | |||
| 07ec14d5c4 | |||
| d716416d1d | |||
| 343871ed8b | |||
| aa60247e42 | |||
| 283e3e99a5 | |||
| ca79bdb16b | |||
| a75d2cfa34 | |||
| 1746f37276 | |||
| 73f32868a2 | |||
| dabd131222 | |||
| 612c6db6db | |||
| c56ef33833 | |||
| 2253e25ae1 | |||
| adb24d480a | |||
| 9fb1dcf28f | |||
| bba36a5724 | |||
| fa515be258 | |||
| be241524db | |||
| bb66c3fa68 | |||
| a32d5bef20 | |||
| d409278dd5 | |||
| 3328e43a40 | |||
| 678e832058 | |||
| 2c341f450f | |||
| 5854074d4a | |||
| dbe186248d | |||
| 0504161b04 | |||
| 9748f1cff8 | |||
| 6a061ed52c | |||
| 102d58502a | |||
| 477698f917 | |||
| d865b5d7b5 | |||
| eeaf6df925 | |||
| 95abca4e03 | |||
| c5906b6f3a | |||
| 91c581b475 | |||
| 47760867d5 | |||
| 5612a5d9e4 | |||
| 4c462bd75a | |||
| 092b30f64f | |||
| b34ca8ca2f | |||
| e2c54eef77 | |||
| 2a2c27edef | |||
| ec92d5ddb7 | |||
| a9f208153c | |||
| d9ffd67f36 | |||
| 19861ef0d1 | |||
| 469879c211 | |||
| 157198fd17 | |||
| 0d61b8db38 | |||
| 593334456a | |||
| 98b9cc23e4 | |||
| 3e42c044b8 | |||
| 11c3ea769e | |||
| b8bb2e234b | |||
| 87b00bb156 | |||
| 3da2fc4d9b | |||
| 972ab9b368 | |||
| c359b0134a | |||
| 2cd7462573 | |||
| 2a7d515932 | |||
| 1bb04035ab | |||
| 267efb0763 | |||
| 0ef215dfc5 | |||
| 50bea8140f | |||
| 086e3ed4ec | |||
| b02539684a | |||
| b9747607ad | |||
| 284a6ae667 | |||
| 116e711f1a | |||
| 7aeb641036 | |||
| e4f69c0b6f | |||
| 48d7228ae7 | |||
| ae28df901f | |||
| f17f45f277 | |||
| 2b15fc2966 | |||
| 76a9342afa | |||
| 14849d6e45 | |||
| 77ea2deada | |||
| 390b7ff834 | |||
| 0e4187b062 | |||
| 4098f77e08 | |||
| a60eed35fe | |||
| 904215fe38 | |||
| 6376642d38 | |||
| 6b36a446f0 | |||
| 95ee7f5c00 | |||
| b109effc94 | |||
| 44efda8318 | |||
| e6e1b6d746 | |||
| e303cbcc22 | |||
| c67aed5b65 | |||
| 0278882c30 | |||
| 42ccd638bd | |||
| e11577bd23 | |||
| 3c0b87bbca | |||
| 84a61b01ca | |||
| c149e008fd | |||
| c7a345eb0b | |||
| 348b6e9742 | |||
| 003b3e02e4 | |||
| 5b668d7931 | |||
| 87748fa80c | |||
| ad0482fb5b | |||
| 12ceb1cb32 | |||
| 9ec2c5da52 | |||
| f742d34588 | |||
| 4d8e058d33 | |||
| 77ba6e0f7b | |||
| 76a0e5c851 | |||
| f3096cc24c | |||
| 49957e1d95 | |||
| 2f5cb5f090 | |||
| ba394e1021 | |||
| 7611c64493 | |||
| 231248d20a | |||
| a3a79fc58d | |||
| 6476e585c4 | |||
| fd2961710d | |||
| 7f4ab67f98 | |||
| b9ce38b85b | |||
| 50d5658add | |||
| 72777bc6cd | |||
| f2046c3c05 | |||
| 745dfc3fbb | |||
| cb77165b53 | |||
| bde4700e87 | |||
| e58cea9a26 | |||
| 556dc0d1ec | |||
| 8c1ddcf1c0 | |||
| 2549c1f97d | |||
| 5faa497821 | |||
| d7a7e72c3a | |||
| af1701e6fa | |||
| 32d1cc7d54 | |||
| 783a615c07 | |||
| 65bfee6eba | |||
| 8d4419705b | |||
| 6c3baf229c | |||
| 6e9a6283fc | |||
| 5b3899237b | |||
| dddf830e47 | |||
| fd930d0b1d | |||
| 2b5d65ae04 | |||
| 2ebaa04c2f | |||
| 1e316ea19f | |||
| ac9257ec1c | |||
| 9b83c5e283 | |||
| a7a4972013 | |||
| f6f4e6fde7 | |||
| e9160c2449 | |||
| b0b1029d0f | |||
| 72b3a0555d | |||
| 135fde68c1 | |||
| 954e45ed97 | |||
| 2b3f16d3ad | |||
| 6820b84921 | |||
| 6a5f5f4ffa | |||
| 19381342b3 | |||
| c2627dda8d | |||
| db309b7930 | |||
| 403958fed3 | |||
| 3af53f2089 | |||
| e472760d92 | |||
| b04acd8ae0 | |||
| 6890973ce8 | |||
| b3d9a85fa2 | |||
| 9f027ed584 | |||
| 8fb598e60a | |||
| 2edaba39a0 | |||
| b0be7effe8 | |||
| 142979ce93 | |||
| 093dd7c62c | |||
| 4acafc3d77 | |||
| 65bf0aad79 | |||
| ef6e846512 | |||
| 782464f664 | |||
| c7352f62e5 | |||
| 90dd6b7cb3 | |||
| ddfb4bf0a5 | |||
| cdef21d6c0 | |||
| c0f843061e | |||
| 5774771ea6 | |||
| ab8d5474e0 | |||
| 6497ec8098 | |||
| 83c3b16b92 | |||
| 3c2bd032ba | |||
| f798866619 | |||
| ffad2c7386 | |||
| 7252e54593 | |||
| cfab4dc658 | |||
| 7f5a8ce6bb | |||
| d02a597451 | |||
| 8d92a1f195 | |||
| 74e630aacb | |||
| 65a12767f9 | |||
| ab9d813636 | |||
| 007975e7da | |||
| 86ca1ebda0 | |||
| 57fb3e6377 | |||
| 7e6fcb80a3 | |||
| 5e46e1e3d9 | |||
| 21e370de9b | |||
| 77ef877c59 | |||
| 77caedb3bb | |||
| 3e77975c17 | |||
| 75e15c81e1 | |||
| 782a1ce301 | |||
| be21b9e163 | |||
| 284140871e | |||
| e6ac40a07c | |||
| b8e98350c1 | |||
| 445ff263c6 | |||
| e10c40d2b8 | |||
| a41a2b3e64 | |||
| e603391c35 | |||
| 7e0cd99f48 | |||
| daedb8261d | |||
| 2c8744a319 | |||
| a7c441225b | |||
| e3c491860a | |||
| 43ad0b2294 | |||
| bf897d10d2 | |||
| 0b1a93d3e6 | |||
| 7edef20f4f | |||
| 945c308cf5 | |||
| f91494f813 | |||
| 9d28caac00 | |||
| 798f3a7b0e | |||
| 768e170ed4 | |||
| a0ebb891de | |||
| 570b39f82e | |||
| dc50899fe0 | |||
| 0f889e0259 | |||
| cb906edd11 | |||
| 604f6709db | |||
| 0359f27cd9 | |||
| 0ca438ed25 | |||
| 6b6e9e92e8 | |||
| b5e0991f5e | |||
| f06f0e7ae0 | |||
| 0fcbb5ffda | |||
| b1f7dbefd8 | |||
| 8fc2d5be37 | |||
| 40020728de | |||
| 4abb169568 | |||
| da1ac5358f | |||
| d504bd593a | |||
| 63e48efdfe | |||
| 8bb27b60fa | |||
| 437c1e2f21 | |||
| 7f4a12c179 | |||
| 19d3bbc70a | |||
| 559561bf72 | |||
| c8c0589ac4 | |||
| 666218773c | |||
| d3049a3433 | |||
| 130d5a8945 | |||
| 172751cd42 | |||
| 3ad7c96a3c | |||
| 6d41d1f6d2 | |||
| cb74833dc2 | |||
| 8c7b6293fb | |||
| 9d1f46da9f | |||
| 216059b659 | |||
| 18392ed0a4 | |||
| 63a4d20ea9 | |||
| 057231b9c3 | |||
| 749bbf428d | |||
| b0458f10a3 | |||
| 5b91c927b6 | |||
| b45740884b | |||
| 87ad4be117 | |||
| 78de70881f | |||
| e7a370a549 | |||
| 54eb579558 | |||
| 732b67d8cb | |||
| eed45b57a1 | |||
| 3503c60fd1 | |||
| c17ba30cfc | |||
| 5167c7235d | |||
| 803f94012a | |||
| 9281bcdd7d | |||
| 4dca554967 | |||
| 7c45fb6c17 | |||
| 8aa283488f | |||
| 604c65c7fb | |||
| 711148423d | |||
| 1f82ceecc6 | |||
| 1ac8701ada | |||
| d61e33fdf3 | |||
| e552b5160f | |||
| 7e063e8ad8 | |||
| 88a34936cd | |||
| c1181478dd | |||
| d13d8628b5 | |||
| 6048208c8c | |||
| 78214fb39b | |||
| ff8d7fa6c2 | |||
| 3a2e8b9b19 | |||
| bca4289c96 | |||
| 3fbd9baf0c | |||
| e12c96f4b2 | |||
| eec26aa481 | |||
| 865aeda6f2 | |||
| 2c4ebedda4 | |||
| 042bc8d79a | |||
| 4c7bd80f72 | |||
| 3a8591fdfb | |||
| 629aaa2093 | |||
| 5b5b118b7a | |||
| c7016aa462 | |||
| cf857e109a | |||
| 1c79840684 | |||
| 4ba7de9519 | |||
| 2eb8df347e | |||
| 9056371c41 | |||
| 1f57e1f366 | |||
| aeb568bcf4 | |||
| b7afe4411e | |||
| cba784b8ec | |||
| 3aba15e88d | |||
| fa384e93dc | |||
| 1f3e04da29 | |||
| a484d48377 | |||
| 15f51ea26e | |||
| 80bfa103ab | |||
| 66f93e0d32 | |||
| 366780f6cb | |||
| fb4c1fc268 | |||
| e3bb7ccbd3 | |||
| e3fb8a2137 |
@@ -0,0 +1,23 @@
|
|||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||||
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
daysUntilClose: 7
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exemptLabels:
|
||||||
|
- acknowledged
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
closeComment: >
|
||||||
|
This issue has been closed due to inactivity.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 1
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ try:
|
|||||||
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
|
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
|
||||||
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
|
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
|
||||||
|
|
||||||
except sqlite3.Error, e:
|
except sqlite3.Error as e:
|
||||||
if connection:
|
if connection:
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
print("Error: %s" % e.args[0])
|
print("Error: %s" % e.args[0])
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ ktlint {
|
|||||||
version = "0.43.2"
|
version = "0.43.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 1019
|
def canonicalVersionCode = 1044
|
||||||
def canonicalVersionName = "5.33.0"
|
def canonicalVersionName = "5.36.3"
|
||||||
|
|
||||||
def postFixSize = 100
|
def postFixSize = 100
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
@@ -455,8 +455,9 @@ dependencies {
|
|||||||
implementation project(':device-transfer')
|
implementation project(':device-transfer')
|
||||||
implementation project(':image-editor')
|
implementation project(':image-editor')
|
||||||
implementation project(':donations')
|
implementation project(':donations')
|
||||||
|
implementation project(':contacts')
|
||||||
|
|
||||||
implementation libs.signal.client.android
|
implementation libs.libsignal.android
|
||||||
implementation libs.google.protobuf.javalite
|
implementation libs.google.protobuf.javalite
|
||||||
|
|
||||||
implementation(libs.mobilecoin) {
|
implementation(libs.mobilecoin) {
|
||||||
@@ -524,10 +525,7 @@ dependencies {
|
|||||||
testImplementation testLibs.junit.junit
|
testImplementation testLibs.junit.junit
|
||||||
testImplementation testLibs.assertj.core
|
testImplementation testLibs.assertj.core
|
||||||
testImplementation testLibs.mockito.core
|
testImplementation testLibs.mockito.core
|
||||||
testImplementation testLibs.powermock.api.mockito
|
testImplementation testLibs.mockito.kotlin
|
||||||
testImplementation testLibs.powermock.module.junit4.core
|
|
||||||
testImplementation testLibs.powermock.module.junit4.rule
|
|
||||||
testImplementation testLibs.powermock.classloading.xstream
|
|
||||||
|
|
||||||
testImplementation testLibs.androidx.test.core
|
testImplementation testLibs.androidx.test.core
|
||||||
testImplementation (testLibs.robolectric.robolectric) {
|
testImplementation (testLibs.robolectric.robolectric) {
|
||||||
@@ -554,7 +552,7 @@ dependencies {
|
|||||||
implementation libs.rxjava3.rxkotlin
|
implementation libs.rxjava3.rxkotlin
|
||||||
implementation libs.rxdogtag
|
implementation libs.rxdogtag
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
def getLastCommitTimestamp() {
|
def getLastCommitTimestamp() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-keepattributes SourceFile,LineNumberTable
|
-keepattributes SourceFile,LineNumberTable
|
||||||
-keep class org.whispersystems.** { *; }
|
-keep class org.whispersystems.** { *; }
|
||||||
|
-keep class org.signal.libsignal.protocol.** { *; }
|
||||||
-keep class org.thoughtcrime.securesms.** { *; }
|
-keep class org.thoughtcrime.securesms.** { *; }
|
||||||
-keepclassmembers class ** {
|
-keepclassmembers class ** {
|
||||||
public void onEvent*(**);
|
public void onEvent*(**);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
|||||||
import org.thoughtcrime.securesms.database.model.StoryType
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.whispersystems.signalservice.api.push.ACI
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import java.lang.IllegalStateException
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class DistributionListDatabaseTest {
|
class DistributionListDatabaseTest {
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Suppress("ClassName")
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MmsDatabaseTest_stories {
|
||||||
|
|
||||||
|
private lateinit var mms: MmsDatabase
|
||||||
|
|
||||||
|
private val localAci = ACI.from(UUID.randomUUID())
|
||||||
|
private val localPni = PNI.from(UUID.randomUUID())
|
||||||
|
|
||||||
|
private lateinit var myStory: Recipient
|
||||||
|
private lateinit var recipients: List<RecipientId>
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mms = SignalDatabase.mms
|
||||||
|
|
||||||
|
mms.deleteAllThreads()
|
||||||
|
|
||||||
|
SignalStore.account().setAci(localAci)
|
||||||
|
SignalStore.account().setPni(localPni)
|
||||||
|
|
||||||
|
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
|
||||||
|
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenNoStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectAnEmptyList() {
|
||||||
|
// WHEN
|
||||||
|
val result = mms.orderedStoryRecipientsAndIds
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertEquals(0, result.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenOneOutgoingAndOneIncomingStory_whenIGetOrderedStoryRecipientsAndIds_thenIExpectIncomingThenOutgoing() {
|
||||||
|
// GIVEN
|
||||||
|
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
|
||||||
|
val sender = recipients[0]
|
||||||
|
|
||||||
|
MmsHelper.insert(
|
||||||
|
recipient = myStory,
|
||||||
|
sentTimeMillis = 1,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES,
|
||||||
|
threadId = threadId
|
||||||
|
)
|
||||||
|
|
||||||
|
MmsHelper.insert(
|
||||||
|
IncomingMediaMessage(
|
||||||
|
from = sender,
|
||||||
|
sentTimeMillis = 2,
|
||||||
|
serverTimeMillis = 2,
|
||||||
|
receivedTimeMillis = 2,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES
|
||||||
|
),
|
||||||
|
-1L
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = mms.orderedStoryRecipientsAndIds
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertEquals(listOf(sender.toLong(), myStory.id.toLong()), result.map { it.recipientId.toLong() })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAStory_whenISetIncomingStoryMessageViewed_thenIExpectASetReceiptTimestamp() {
|
||||||
|
// GIVEN
|
||||||
|
val sender = recipients[0]
|
||||||
|
val messageId = MmsHelper.insert(
|
||||||
|
IncomingMediaMessage(
|
||||||
|
from = sender,
|
||||||
|
sentTimeMillis = 2,
|
||||||
|
serverTimeMillis = 2,
|
||||||
|
receivedTimeMillis = 2,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES
|
||||||
|
),
|
||||||
|
-1L
|
||||||
|
).get().messageId
|
||||||
|
|
||||||
|
val messageBeforeMark = SignalDatabase.mms.getMessageRecord(messageId)
|
||||||
|
assertFalse(messageBeforeMark.incomingStoryViewedAtTimestamp > 0)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
SignalDatabase.mms.setIncomingMessageViewed(messageId)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val messageAfterMark = SignalDatabase.mms.getMessageRecord(messageId)
|
||||||
|
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
|
||||||
|
// GIVEN
|
||||||
|
val messageIds = recipients.take(5).map {
|
||||||
|
MmsHelper.insert(
|
||||||
|
IncomingMediaMessage(
|
||||||
|
from = it,
|
||||||
|
sentTimeMillis = 2,
|
||||||
|
serverTimeMillis = 2,
|
||||||
|
receivedTimeMillis = 2,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES,
|
||||||
|
),
|
||||||
|
-1L
|
||||||
|
).get().messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
val randomizedOrderedIds = messageIds.shuffled()
|
||||||
|
randomizedOrderedIds.forEach {
|
||||||
|
SignalDatabase.mms.setIncomingMessageViewed(it)
|
||||||
|
Thread.sleep(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = SignalDatabase.mms.orderedStoryRecipientsAndIds
|
||||||
|
val resultOrderedIds = result.map { it.messageId }
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertEquals(randomizedOrderedIds.reversed(), resultOrderedIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun given15Stories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectUnviewedThenInterspersedViewedAndSelfSendsAllDescending() {
|
||||||
|
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
|
||||||
|
|
||||||
|
val unviewedIds: List<Long> = (0 until 5).map {
|
||||||
|
Thread.sleep(5)
|
||||||
|
MmsHelper.insert(
|
||||||
|
IncomingMediaMessage(
|
||||||
|
from = recipients[it],
|
||||||
|
sentTimeMillis = System.currentTimeMillis(),
|
||||||
|
serverTimeMillis = 2,
|
||||||
|
receivedTimeMillis = 2,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES,
|
||||||
|
),
|
||||||
|
-1L
|
||||||
|
).get().messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
val viewedIds: List<Long> = (0 until 5).map {
|
||||||
|
Thread.sleep(5)
|
||||||
|
MmsHelper.insert(
|
||||||
|
IncomingMediaMessage(
|
||||||
|
from = recipients[it],
|
||||||
|
sentTimeMillis = System.currentTimeMillis(),
|
||||||
|
serverTimeMillis = 2,
|
||||||
|
receivedTimeMillis = 2,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES,
|
||||||
|
),
|
||||||
|
-1L
|
||||||
|
).get().messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
val interspersedIds: List<Long> = (0 until 10).map {
|
||||||
|
Thread.sleep(5)
|
||||||
|
if (it % 2 == 0) {
|
||||||
|
SignalDatabase.mms.setIncomingMessageViewed(viewedIds[it / 2])
|
||||||
|
viewedIds[it / 2]
|
||||||
|
} else {
|
||||||
|
MmsHelper.insert(
|
||||||
|
recipient = myStory,
|
||||||
|
sentTimeMillis = System.currentTimeMillis(),
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES,
|
||||||
|
threadId = myStoryThread
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = SignalDatabase.mms.orderedStoryRecipientsAndIds
|
||||||
|
val resultOrderedIds = result.map { it.messageId }
|
||||||
|
|
||||||
|
assertEquals(unviewedIds.reversed() + interspersedIds.reversed(), resultOrderedIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper methods for inserting an MMS message into the MMS table.
|
||||||
|
*/
|
||||||
|
object MmsHelper {
|
||||||
|
|
||||||
|
fun insert(
|
||||||
|
recipient: Recipient = Recipient.UNKNOWN,
|
||||||
|
body: String = "body",
|
||||||
|
sentTimeMillis: Long = System.currentTimeMillis(),
|
||||||
|
subscriptionId: Int = -1,
|
||||||
|
expiresIn: Long = 0,
|
||||||
|
viewOnce: Boolean = false,
|
||||||
|
distributionType: Int = ThreadDatabase.DistributionTypes.DEFAULT,
|
||||||
|
threadId: Long = 1,
|
||||||
|
storyType: StoryType = StoryType.NONE
|
||||||
|
): Long {
|
||||||
|
val message = OutgoingMediaMessage(
|
||||||
|
recipient,
|
||||||
|
body,
|
||||||
|
emptyList(),
|
||||||
|
sentTimeMillis,
|
||||||
|
subscriptionId,
|
||||||
|
expiresIn,
|
||||||
|
viewOnce,
|
||||||
|
distributionType,
|
||||||
|
storyType,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
emptySet(),
|
||||||
|
emptySet()
|
||||||
|
)
|
||||||
|
|
||||||
|
return insert(
|
||||||
|
message = message,
|
||||||
|
threadId = threadId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(
|
||||||
|
message: OutgoingMediaMessage,
|
||||||
|
threadId: Long
|
||||||
|
): Long {
|
||||||
|
return SignalDatabase.mms.insertMessageOutbox(message, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(
|
||||||
|
message: IncomingMediaMessage,
|
||||||
|
threadId: Long
|
||||||
|
): Optional<MessageDatabase.InsertResult> {
|
||||||
|
return SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, threadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,9 @@ import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.whispersystems.libsignal.util.guava.Optional
|
|
||||||
import org.whispersystems.signalservice.api.push.ACI
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import org.whispersystems.signalservice.api.push.PNI
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
import java.lang.IllegalArgumentException
|
import java.util.Optional
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -523,7 +522,7 @@ class RecipientDatabaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureDbEmpty() {
|
private fun ensureDbEmpty() {
|
||||||
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor ->
|
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME} WHERE ${RecipientDatabase.DISTRIBUTION_LIST_ID} IS NULL ", null).use { cursor ->
|
||||||
assertTrue(cursor.moveToFirst())
|
assertTrue(cursor.moveToFirst())
|
||||||
assertEquals(0, cursor.getLong(0))
|
assertEquals(0, cursor.getLong(0))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ import org.junit.Assert.assertEquals
|
|||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.signal.core.util.CursorUtil
|
||||||
|
import org.signal.libsignal.protocol.IdentityKey
|
||||||
|
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||||
|
import org.signal.libsignal.protocol.state.SessionRecord
|
||||||
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey
|
|
||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
||||||
@@ -30,14 +33,10 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||||
import org.thoughtcrime.securesms.util.CursorUtil
|
|
||||||
import org.whispersystems.libsignal.IdentityKey
|
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress
|
|
||||||
import org.whispersystems.libsignal.state.SessionRecord
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional
|
|
||||||
import org.whispersystems.signalservice.api.push.ACI
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import org.whispersystems.signalservice.api.push.PNI
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||||
|
import java.util.Optional
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -76,8 +75,6 @@ class RecipientDatabaseTest_merges {
|
|||||||
|
|
||||||
SignalStore.account().setAci(localAci)
|
SignalStore.account().setAci(localAci)
|
||||||
SignalStore.account().setPni(localPni)
|
SignalStore.account().setPni(localPni)
|
||||||
|
|
||||||
ensureDbEmpty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
|
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
|
||||||
@@ -217,19 +214,12 @@ class RecipientDatabaseTest_merges {
|
|||||||
private val context: Application
|
private val context: Application
|
||||||
get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
|
get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
|
||||||
|
|
||||||
private fun ensureDbEmpty() {
|
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
|
||||||
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor ->
|
|
||||||
assertTrue(cursor.moveToFirst())
|
|
||||||
assertEquals(0, cursor.getLong(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingTextMessage {
|
|
||||||
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
|
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingMediaMessage {
|
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
|
||||||
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.absent())
|
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun identityKey(value: Byte): IdentityKey {
|
private fun identityKey(value: Byte): IdentityKey {
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
|
import org.hamcrest.Matchers.`is`
|
||||||
|
import org.hamcrest.Matchers.notNullValue
|
||||||
|
import org.hamcrest.Matchers.nullValue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.signal.core.util.Hex
|
||||||
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
|
||||||
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import java.util.Optional
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Suppress("ClassName", "TestFunctionName")
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||||
|
|
||||||
|
private lateinit var recipients: RecipientDatabase
|
||||||
|
private lateinit var sms: SmsDatabase
|
||||||
|
|
||||||
|
private val localAci = ACI.from(UUID.randomUUID())
|
||||||
|
private val localPni = PNI.from(UUID.randomUUID())
|
||||||
|
|
||||||
|
private var wallClock: Long = 1000
|
||||||
|
|
||||||
|
private lateinit var alice: RecipientId
|
||||||
|
private lateinit var bob: RecipientId
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
recipients = SignalDatabase.recipients
|
||||||
|
sms = SignalDatabase.sms
|
||||||
|
|
||||||
|
SignalStore.account().setAci(localAci)
|
||||||
|
SignalStore.account().setPni(localPni)
|
||||||
|
|
||||||
|
alice = recipients.getOrInsertFromServiceId(aliceServiceId)
|
||||||
|
bob = recipients.getOrInsertFromServiceId(bobServiceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do nothing if no previous messages.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun noPreviousMessage() {
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
1,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do nothing if previous message is text.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousTextMesssage() {
|
||||||
|
val threadId = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get().threadId
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do nothing if previous is unrelated group change.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousUnrelatedGroupChange() {
|
||||||
|
val threadId = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
addMember(bobServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get().threadId
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do nothing if previous join request is from a different recipient.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousJoinRequestFromADifferentRecipient() {
|
||||||
|
val threadId = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = bob,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = bobServiceId) {
|
||||||
|
deleteRequestingMember(bobServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get().threadId
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse if previous is join request from same.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousJoinRequestCollapse() {
|
||||||
|
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
addRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get()
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
latestMessage.threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
|
||||||
|
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse if previous is join request from same, and leave second previous alone if text.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousJoinThenTextCollapse() {
|
||||||
|
val secondLatestMessage = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get()
|
||||||
|
|
||||||
|
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
addRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get()
|
||||||
|
|
||||||
|
assert(secondLatestMessage.threadId == latestMessage.threadId)
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
latestMessage.threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
|
||||||
|
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse "twice" is previous is a join request and second previous is already collapsed join/delete from the same recipient.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun previousCollapseAndJoinRequestDoubleCollapse() {
|
||||||
|
val secondLatestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
addRequestingMember(aliceServiceId)
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get()
|
||||||
|
|
||||||
|
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
addRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).get()
|
||||||
|
|
||||||
|
assert(secondLatestMessage.threadId == latestMessage.threadId)
|
||||||
|
|
||||||
|
val result = sms.collapseJoinRequestEventsIfPossible(
|
||||||
|
latestMessage.threadId,
|
||||||
|
groupUpdateMessage(
|
||||||
|
sender = alice,
|
||||||
|
groupContext = groupContext(masterKey = masterKey) {
|
||||||
|
change = groupChange(editor = aliceServiceId) {
|
||||||
|
deleteRequestingMember(aliceServiceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
|
||||||
|
assertThat("result message id should be same as second latest message", result.get().messageId, `is`(secondLatestMessage.messageId))
|
||||||
|
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
|
||||||
|
wallClock++
|
||||||
|
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
|
||||||
|
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
|
||||||
|
private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
|
||||||
|
|
||||||
|
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||||
|
private val groupId = GroupId.v2(masterKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
|
import org.hamcrest.Matchers.containsInAnyOrder
|
||||||
|
import org.hamcrest.Matchers.hasSize
|
||||||
|
import org.hamcrest.Matchers.`is`
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class StorySendsDatabaseTest {
|
||||||
|
|
||||||
|
private lateinit var recipients1to10: List<RecipientId>
|
||||||
|
private lateinit var recipients11to20: List<RecipientId>
|
||||||
|
private lateinit var recipients6to15: List<RecipientId>
|
||||||
|
private lateinit var recipients6to10: List<RecipientId>
|
||||||
|
|
||||||
|
private var messageId1: Long = 0
|
||||||
|
private var messageId2: Long = 0
|
||||||
|
private var messageId3: Long = 0
|
||||||
|
|
||||||
|
private lateinit var storySends: StorySendsDatabase
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
storySends = SignalDatabase.storySends
|
||||||
|
|
||||||
|
messageId1 = MmsHelper.insert(storyType = StoryType.STORY_WITHOUT_REPLIES)
|
||||||
|
messageId2 = MmsHelper.insert(storyType = StoryType.STORY_WITH_REPLIES)
|
||||||
|
messageId3 = MmsHelper.insert(storyType = StoryType.STORY_WITHOUT_REPLIES)
|
||||||
|
|
||||||
|
recipients1to10 = makeRecipients(10)
|
||||||
|
recipients11to20 = makeRecipients(10)
|
||||||
|
|
||||||
|
recipients6to15 = recipients1to10.takeLast(5) + recipients11to20.take(5)
|
||||||
|
recipients6to10 = recipients1to10.takeLast(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRecipientsToSendTo_noOverlap() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 100, false)
|
||||||
|
storySends.insert(messageId2, recipients11to20, 200, true)
|
||||||
|
storySends.insert(messageId3, recipients1to10, 300, false)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 200, true)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRecipientsToSendTo_overlap() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 100, false)
|
||||||
|
storySends.insert(messageId2, recipients6to15, 100, true)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(5))
|
||||||
|
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRecipientsToSendTo_overlapAll() {
|
||||||
|
val recipient1 = recipients1to10.first()
|
||||||
|
val recipient2 = recipients11to20.first()
|
||||||
|
|
||||||
|
storySends.insert(messageId1, listOf(recipient1, recipient2), 100, false)
|
||||||
|
storySends.insert(messageId2, listOf(recipient1), 100, true)
|
||||||
|
storySends.insert(messageId3, listOf(recipient2), 100, true)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
|
||||||
|
val recipientIdsForMessage3 = storySends.getRecipientsToSendTo(messageId3, 100, true)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(0))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(1))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(recipient1))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage3, hasSize(1))
|
||||||
|
assertThat(recipientIdsForMessage3, containsInAnyOrder(recipient2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRecipientsToSendTo_overlapWithEarlierMessage() {
|
||||||
|
storySends.insert(messageId1, recipients6to15, 100, true)
|
||||||
|
storySends.insert(messageId2, recipients1to10, 100, false)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, true)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, false)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients6to15.toTypedArray()))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(5))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRemoteDeleteRecipients_noOverlap() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 100, false)
|
||||||
|
storySends.insert(messageId2, recipients11to20, 200, true)
|
||||||
|
storySends.insert(messageId3, recipients1to10, 300, false)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 100)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRemoteDeleteRecipients_overlapNoPreviousDeletes() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 200, false)
|
||||||
|
storySends.insert(messageId2, recipients6to15, 200, true)
|
||||||
|
|
||||||
|
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 200)
|
||||||
|
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage1, hasSize(5))
|
||||||
|
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(5))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.takeLast(5).toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRemoteDeleteRecipients_overlapWithPreviousDeletes() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 200, false)
|
||||||
|
SignalDatabase.mms.markAsRemoteDelete(messageId1)
|
||||||
|
|
||||||
|
storySends.insert(messageId2, recipients6to15, 200, true)
|
||||||
|
|
||||||
|
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
|
||||||
|
|
||||||
|
assertThat(recipientIdsForMessage2, hasSize(10))
|
||||||
|
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun canReply_storyWithReplies() {
|
||||||
|
storySends.insert(messageId2, recipients1to10, 200, true)
|
||||||
|
|
||||||
|
val canReply = storySends.canReply(recipients1to10[0], 200)
|
||||||
|
|
||||||
|
assertThat(canReply, `is`(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun canReply_storyWithoutReplies() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 200, false)
|
||||||
|
|
||||||
|
val canReply = storySends.canReply(recipients1to10[0], 200)
|
||||||
|
|
||||||
|
assertThat(canReply, `is`(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun canReply_storyWithAndWithoutRepliesOverlap() {
|
||||||
|
storySends.insert(messageId1, recipients1to10, 200, false)
|
||||||
|
storySends.insert(messageId2, recipients6to10, 200, true)
|
||||||
|
|
||||||
|
val message1OnlyRecipientCanReply = storySends.canReply(recipients1to10[0], 200)
|
||||||
|
val message2RecipientCanReply = storySends.canReply(recipients6to10[0], 200)
|
||||||
|
|
||||||
|
assertThat(message1OnlyRecipientCanReply, `is`(false))
|
||||||
|
assertThat(message2RecipientCanReply, `is`(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeRecipients(count: Int): List<RecipientId> {
|
||||||
|
return (1..count).map {
|
||||||
|
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.thoughtcrime.securesms.util.Hex;
|
import org.signal.core.util.Hex;
|
||||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||||
import org.whispersystems.signalservice.api.kbs.KbsData;
|
import org.whispersystems.signalservice.api.kbs.KbsData;
|
||||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||||
|
|||||||
@@ -179,11 +179,12 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".sharing.ShareActivity"
|
<activity android:name=".sharing.v2.ShareActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:exported="true"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
@@ -403,7 +404,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".stories.viewer.StoryViewerActivity"
|
android:name=".stories.viewer.StoryViewerActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
|
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
|
||||||
|
|
||||||
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
||||||
@@ -757,6 +758,8 @@
|
|||||||
|
|
||||||
<receiver android:name=".service.ExpirationListener" />
|
<receiver android:name=".service.ExpirationListener" />
|
||||||
|
|
||||||
|
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
|
||||||
|
|
||||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||||
|
|
||||||
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
|
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
package com.google.android.material.bottomsheet
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually adjust the nested scrolling child for a given [BottomSheetBehavior].
|
||||||
|
*/
|
||||||
|
object BottomSheetBehaviorHack {
|
||||||
|
fun setNestedScrollingChild(behavior: BottomSheetBehavior<FrameLayout>, view: View) {
|
||||||
|
behavior.nestedScrollingChildRef = WeakReference(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ import org.signal.core.util.logging.AndroidLogger;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.core.util.tracing.Tracer;
|
import org.signal.core.util.tracing.Tracer;
|
||||||
import org.signal.glide.SignalGlideCodecs;
|
import org.signal.glide.SignalGlideCodecs;
|
||||||
|
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||||
@@ -53,6 +54,7 @@ import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
@@ -91,7 +93,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
|
||||||
|
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
@@ -201,6 +202,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
|
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
|
||||||
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
|
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
|
||||||
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
|
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
|
||||||
|
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.widget.ImageView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
@@ -58,6 +59,12 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
|||||||
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
|
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(@NonNull Context newBase) {
|
||||||
|
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||||
|
super.attachBaseContext(newBase);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
super.onCreate(savedInstanceState, ready);
|
super.onCreate(savedInstanceState, ready);
|
||||||
@@ -132,7 +139,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
|||||||
|
|
||||||
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
|
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
|
||||||
|
|
||||||
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer));
|
fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), toolbar);
|
||||||
|
|
||||||
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable {
|
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable {
|
||||||
@@ -60,6 +60,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
|||||||
// Intentionally Blank.
|
// Intentionally Blank.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void updateSelectedState() {
|
||||||
|
// Intentionall Blank.
|
||||||
|
}
|
||||||
|
|
||||||
interface EventListener {
|
interface EventListener {
|
||||||
void onQuoteClicked(MmsMessageRecord messageRecord);
|
void onQuoteClicked(MmsMessageRecord messageRecord);
|
||||||
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||||
@@ -94,6 +98,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
|||||||
void onChangeNumberUpdateContact(@NonNull Recipient recipient);
|
void onChangeNumberUpdateContact(@NonNull Recipient recipient);
|
||||||
void onCallToAction(@NonNull String action);
|
void onCallToAction(@NonNull String action);
|
||||||
void onDonateClicked();
|
void onDonateClicked();
|
||||||
|
void onBlockJoinRequest(@NonNull Recipient recipient);
|
||||||
|
void onRecipientNameClicked(@NonNull RecipientId target);
|
||||||
|
|
||||||
/** @return true if handled, false if you want to let the normal url handling continue */
|
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||||
boolean onUrlClicked(@NonNull String url);
|
boolean onUrlClicked(@NonNull String url);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,7 +153,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Context... params) {
|
protected Void doInBackground(Context... params) {
|
||||||
try {
|
try {
|
||||||
DirectoryHelper.refreshDirectory(params[0], true);
|
ContactDiscovery.refreshAll(params[0], true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
|||||||
import org.thoughtcrime.securesms.contacts.HeaderAction;
|
import org.thoughtcrime.securesms.contacts.HeaderAction;
|
||||||
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
|
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@@ -85,13 +85,13 @@ import org.thoughtcrime.securesms.util.UsernameUtil;
|
|||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||||
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -576,7 +576,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
try {
|
try {
|
||||||
DirectoryHelper.refreshDirectory(context, false);
|
ContactDiscovery.refreshAll(context, false);
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
@@ -637,8 +637,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(ContactSelectionListItem contact) {
|
public void onItemClick(ContactSelectionListItem contact) {
|
||||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orElse(null), contact.getNumber())
|
||||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
: SelectedContact.forPhone(contact.getRecipientId().orElse(null), contact.getNumber());
|
||||||
|
|
||||||
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||||
@@ -774,7 +774,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
markContactUnselected(selectedContact);
|
markContactUnselected(selectedContact);
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orNull());
|
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orElse(null));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
|
import org.signal.libsignal.protocol.ecc.Curve;
|
||||||
|
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
@@ -34,11 +36,6 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||||
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
|
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
for (SelectedContact contact : contacts) {
|
for (SelectedContact contact : contacts) {
|
||||||
RecipientId recipientId = contact.getOrCreateRecipientId(context);
|
RecipientId recipientId = contact.getOrCreateRecipientId(context);
|
||||||
Recipient recipient = Recipient.resolved(recipientId);
|
Recipient recipient = Recipient.resolved(recipientId);
|
||||||
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
|
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
|
||||||
|
|
||||||
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null);
|
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.view.View;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.lifecycle.Transformations;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
|
||||||
@@ -19,14 +18,12 @@ import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLock
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.stories.Stories;
|
import org.thoughtcrime.securesms.stories.Stories;
|
||||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
|
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
|
||||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsState;
|
|
||||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
|
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||||
|
|
||||||
public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner {
|
public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner {
|
||||||
@@ -61,8 +58,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
|||||||
ConversationListTabRepository repository = new ConversationListTabRepository();
|
ConversationListTabRepository repository = new ConversationListTabRepository();
|
||||||
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
|
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
|
||||||
|
|
||||||
navigator.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
handleGroupLinkInIntent(getIntent());
|
handleGroupLinkInIntent(getIntent());
|
||||||
handleProxyInIntent(getIntent());
|
handleProxyInIntent(getIntent());
|
||||||
handleSignalMeIntent(getIntent());
|
handleSignalMeIntent(getIntent());
|
||||||
@@ -70,18 +65,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
|||||||
CachedInflater.from(this).clear();
|
CachedInflater.from(this).clear();
|
||||||
|
|
||||||
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
|
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
|
||||||
Transformations.map(conversationListTabsViewModel.getState(), ConversationListTabsState::getTab)
|
|
||||||
.observe(this, tab -> {
|
|
||||||
switch (tab) {
|
|
||||||
case CHATS:
|
|
||||||
getSupportFragmentManager().popBackStack();
|
|
||||||
break;
|
|
||||||
case STORIES:
|
|
||||||
navigator.goToStories();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateTabVisibility();
|
updateTabVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,11 +118,11 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
|||||||
private void updateTabVisibility() {
|
private void updateTabVisibility() {
|
||||||
if (Stories.isFeatureEnabled()) {
|
if (Stories.isFeatureEnabled()) {
|
||||||
findViewById(R.id.conversation_list_tabs).setVisibility(View.VISIBLE);
|
findViewById(R.id.conversation_list_tabs).setVisibility(View.VISIBLE);
|
||||||
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_secondary));
|
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_colorSecondaryContainer));
|
||||||
} else {
|
} else {
|
||||||
findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE);
|
findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE);
|
||||||
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_primary));
|
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_primary));
|
||||||
navigator.goToChats();
|
conversationListTabsViewModel.onChatsSelected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,19 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
|
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
|
||||||
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment;
|
|
||||||
|
|
||||||
public class MainNavigator {
|
public class MainNavigator {
|
||||||
|
|
||||||
public static final String STORIES_TAG = "STORIES";
|
|
||||||
|
|
||||||
public static final int REQUEST_CONFIG_CHANGES = 901;
|
public static final int REQUEST_CONFIG_CHANGES = 901;
|
||||||
|
|
||||||
private final MainActivity activity;
|
private final MainActivity activity;
|
||||||
@@ -38,16 +31,6 @@ public class MainNavigator {
|
|||||||
return ((MainActivity) activity).getNavigator();
|
return ((MainActivity) activity).getNavigator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.add(R.id.fragment_container, ConversationListFragment.newInstance())
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return True if the back pressed was handled in our own custom way, false if it should be given
|
* @return True if the back pressed was handled in our own custom way, false if it should be given
|
||||||
* to the system to do the default behavior.
|
* to the system to do the default behavior.
|
||||||
@@ -76,29 +59,6 @@ public class MainNavigator {
|
|||||||
activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES);
|
activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToArchiveList() {
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
|
||||||
.replace(R.id.fragment_container, ConversationListArchiveFragment.newInstance())
|
|
||||||
.addToBackStack(null)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goToStories() {
|
|
||||||
if (getFragmentManager().findFragmentByTag(STORIES_TAG) == null) {
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.fragment_container, new StoriesLandingFragment(), STORIES_TAG)
|
|
||||||
.addToBackStack(null)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goToChats() {
|
|
||||||
if (getFragmentManager().findFragmentByTag(STORIES_TAG) != null) {
|
|
||||||
getFragmentManager().popBackStack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goToGroupCreation() {
|
public void goToGroupCreation() {
|
||||||
activity.startActivity(CreateGroupActivity.newIntent(activity));
|
activity.startActivity(CreateGroupActivity.newIntent(activity));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ import org.signal.core.util.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.animation.DepthPageTransformer;
|
import org.thoughtcrime.securesms.animation.DepthPageTransformer;
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
|
||||||
|
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment;
|
||||||
|
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||||
@@ -67,7 +71,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.sharing.ShareActivity;
|
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||||
@@ -87,7 +90,8 @@ import java.util.Objects;
|
|||||||
public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||||
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
|
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
|
||||||
MediaRailAdapter.RailItemListener,
|
MediaRailAdapter.RailItemListener,
|
||||||
MediaPreviewFragment.Events
|
MediaPreviewFragment.Events,
|
||||||
|
VoiceNoteMediaControllerOwner
|
||||||
{
|
{
|
||||||
|
|
||||||
private final static String TAG = Log.tag(MediaPreviewActivity.class);
|
private final static String TAG = Log.tag(MediaPreviewActivity.class);
|
||||||
@@ -127,6 +131,8 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
private MediaDatabase.Sorting sorting;
|
private MediaDatabase.Sorting sorting;
|
||||||
private FullscreenHelper fullscreenHelper;
|
private FullscreenHelper fullscreenHelper;
|
||||||
|
|
||||||
|
private VoiceNoteMediaController voiceNoteMediaController;
|
||||||
|
|
||||||
private @Nullable Cursor cursor = null;
|
private @Nullable Cursor cursor = null;
|
||||||
|
|
||||||
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
|
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
|
||||||
@@ -159,6 +165,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
|
voiceNoteMediaController = new VoiceNoteMediaController(this);
|
||||||
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
||||||
|
|
||||||
fullscreenHelper = new FullscreenHelper(this);
|
fullscreenHelper = new FullscreenHelper(this);
|
||||||
@@ -203,23 +210,25 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
else from = "";
|
else from = "";
|
||||||
|
|
||||||
if (showThread) {
|
if (showThread) {
|
||||||
String to = null;
|
String titleText = null;
|
||||||
Recipient threadRecipient = mediaItem.threadRecipient;
|
Recipient threadRecipient = mediaItem.threadRecipient;
|
||||||
|
|
||||||
if (threadRecipient != null) {
|
if (threadRecipient != null) {
|
||||||
if (mediaItem.outgoing || threadRecipient.isGroup()) {
|
if (mediaItem.outgoing) {
|
||||||
if (threadRecipient.isSelf()) {
|
if (threadRecipient.isSelf()) {
|
||||||
from = getString(R.string.note_to_self);
|
titleText = getString(R.string.note_to_self);
|
||||||
} else {
|
} else {
|
||||||
to = threadRecipient.getDisplayName(this);
|
titleText = getString(R.string.MediaPreviewActivity_you_to_s, threadRecipient.getDisplayName(this));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
to = getString(R.string.MediaPreviewActivity_you);
|
if (threadRecipient.isGroup()) {
|
||||||
|
titleText = getString(R.string.MediaPreviewActivity_s_to_s, from, threadRecipient.getDisplayName(this));
|
||||||
|
} else {
|
||||||
|
titleText = getString(R.string.MediaPreviewActivity_s_to_you, from);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return titleText != null ? titleText : from;
|
||||||
return to != null ? getString(R.string.MediaPreviewActivity_s_to_s, from, to)
|
|
||||||
: from;
|
|
||||||
} else {
|
} else {
|
||||||
return from;
|
return from;
|
||||||
}
|
}
|
||||||
@@ -286,7 +295,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
anchorMarginsToBottomInsets(detailsContainer);
|
anchorMarginsToBottomInsets(detailsContainer);
|
||||||
|
|
||||||
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer));
|
fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), findViewById(R.id.toolbar));
|
||||||
|
|
||||||
fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
|
fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
|
||||||
}
|
}
|
||||||
@@ -386,10 +395,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
MediaItem mediaItem = getCurrentMediaItem();
|
MediaItem mediaItem = getCurrentMediaItem();
|
||||||
|
|
||||||
if (mediaItem != null) {
|
if (mediaItem != null) {
|
||||||
Intent composeIntent = new Intent(this, ShareActivity.class);
|
MultiselectForwardFragmentArgs.create(
|
||||||
composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri);
|
this,
|
||||||
composeIntent.setType(mediaItem.type);
|
mediaItem.uri,
|
||||||
startActivity(composeIntent);
|
mediaItem.type,
|
||||||
|
args -> MultiselectForwardFragment.showBottomSheet(getSupportFragmentManager(), args)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,6 +608,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMediaReady() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() {
|
||||||
|
return voiceNoteMediaController;
|
||||||
|
}
|
||||||
|
|
||||||
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
||||||
@@ -33,11 +33,11 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,7 +79,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
|
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
|
||||||
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
|
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
|
||||||
try {
|
try {
|
||||||
DirectoryHelper.refreshDirectoryFor(this, resolved, false);
|
ContactDiscovery.refresh(this, resolved, false);
|
||||||
resolved = Recipient.resolved(resolved.getId());
|
resolved = Recipient.resolved(resolved.getId());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
|
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
|||||||
passphrase);
|
passphrase);
|
||||||
|
|
||||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||||
SignalStore.account().generateAciIdentityKey();
|
SignalStore.account().generateAciIdentityKeyIfNecessary();
|
||||||
SignalStore.account().generatePniIdentityKeyIfNecessary();
|
SignalStore.account().generatePniIdentityKeyIfNecessary();
|
||||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||||
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
public class TransportOption implements Parcelable {
|
public class TransportOption implements Parcelable {
|
||||||
|
|
||||||
@@ -18,8 +20,8 @@ public class TransportOption implements Parcelable {
|
|||||||
TEXTSECURE
|
TEXTSECURE
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int drawable;
|
private final int drawable;
|
||||||
private final int backgroundColor;
|
private final int backgroundColor;
|
||||||
private final @NonNull String text;
|
private final @NonNull String text;
|
||||||
private final @NonNull Type type;
|
private final @NonNull Type type;
|
||||||
private final @NonNull String composeHint;
|
private final @NonNull String composeHint;
|
||||||
@@ -35,7 +37,7 @@ public class TransportOption implements Parcelable {
|
|||||||
@NonNull CharacterCalculator characterCalculator)
|
@NonNull CharacterCalculator characterCalculator)
|
||||||
{
|
{
|
||||||
this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
|
this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
|
||||||
Optional.<CharSequence>absent(), Optional.<Integer>absent());
|
Optional.empty(), Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransportOption(@NonNull Type type,
|
public TransportOption(@NonNull Type type,
|
||||||
@@ -64,8 +66,8 @@ public class TransportOption implements Parcelable {
|
|||||||
in.readString(),
|
in.readString(),
|
||||||
in.readString(),
|
in.readString(),
|
||||||
CharacterCalculator.readFromParcel(in),
|
CharacterCalculator.readFromParcel(in),
|
||||||
Optional.fromNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
|
Optional.ofNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
|
||||||
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.absent());
|
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Type getType() {
|
public @NonNull Type getType() {
|
||||||
@@ -123,7 +125,7 @@ public class TransportOption implements Parcelable {
|
|||||||
dest.writeString(text);
|
dest.writeString(text);
|
||||||
dest.writeString(composeHint);
|
dest.writeString(composeHint);
|
||||||
CharacterCalculator.writeToParcel(dest, characterCalculator);
|
CharacterCalculator.writeToParcel(dest, characterCalculator);
|
||||||
TextUtils.writeToParcel(simName.orNull(), dest, flags);
|
TextUtils.writeToParcel(simName.orElse(null), dest, flags);
|
||||||
|
|
||||||
if (simSubscriptionId.isPresent()) {
|
if (simSubscriptionId.isPresent()) {
|
||||||
dest.writeInt(1);
|
dest.writeInt(1);
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import org.thoughtcrime.securesms.util.PushCharacterCalculator;
|
|||||||
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
|
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||||
|
|
||||||
@@ -33,8 +34,8 @@ public class TransportOptions {
|
|||||||
private final List<TransportOption> enabledTransports;
|
private final List<TransportOption> enabledTransports;
|
||||||
|
|
||||||
private Type defaultTransportType = Type.SMS;
|
private Type defaultTransportType = Type.SMS;
|
||||||
private Optional<Integer> defaultSubscriptionId = Optional.absent();
|
private Optional<Integer> defaultSubscriptionId = Optional.empty();
|
||||||
private Optional<TransportOption> selectedOption = Optional.absent();
|
private Optional<TransportOption> selectedOption = Optional.empty();
|
||||||
|
|
||||||
private final Optional<Integer> systemSubscriptionId;
|
private final Optional<Integer> systemSubscriptionId;
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ public class TransportOptions {
|
|||||||
setSelectedTransport(null);
|
setSelectedTransport(null);
|
||||||
} else {
|
} else {
|
||||||
this.defaultTransportType = Type.SMS;
|
this.defaultTransportType = Type.SMS;
|
||||||
this.defaultSubscriptionId = Optional.absent();
|
this.defaultSubscriptionId = Optional.empty();
|
||||||
|
|
||||||
notifyTransportChangeListeners();
|
notifyTransportChangeListeners();
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,7 @@ public class TransportOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedTransport(@Nullable TransportOption transportOption) {
|
public void setSelectedTransport(@Nullable TransportOption transportOption) {
|
||||||
this.selectedOption = Optional.fromNullable(transportOption);
|
this.selectedOption = Optional.ofNullable(transportOption);
|
||||||
notifyTransportChangeListeners();
|
notifyTransportChangeListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ public class TransportOptions {
|
|||||||
if (selectedOption.isPresent()) return selectedOption.get();
|
if (selectedOption.isPresent()) return selectedOption.get();
|
||||||
|
|
||||||
if (defaultTransportType == Type.SMS) {
|
if (defaultTransportType == Type.SMS) {
|
||||||
TransportOption transportOption = findEnabledSmsTransportOption(defaultSubscriptionId.or(systemSubscriptionId));
|
TransportOption transportOption = findEnabledSmsTransportOption(OptionalUtil.or(defaultSubscriptionId, systemSubscriptionId));
|
||||||
if (transportOption != null) {
|
if (transportOption != null) {
|
||||||
return transportOption;
|
return transportOption;
|
||||||
}
|
}
|
||||||
@@ -124,7 +125,7 @@ public class TransportOptions {
|
|||||||
|
|
||||||
for (TransportOption transportOption : enabledTransports) {
|
for (TransportOption transportOption : enabledTransports) {
|
||||||
if (transportOption.getType() == Type.SMS &&
|
if (transportOption.getType() == Type.SMS &&
|
||||||
subId == transportOption.getSimSubscriptionId().or(-1)) {
|
subId == transportOption.getSimSubscriptionId().orElse(-1)) {
|
||||||
return transportOption;
|
return transportOption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ public class TransportOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void disableTransport(Type type) {
|
public void disableTransport(Type type) {
|
||||||
TransportOption selected = selectedOption.orNull();
|
TransportOption selected = selectedOption.orElse(null);
|
||||||
|
|
||||||
Iterator<TransportOption> iterator = enabledTransports.iterator();
|
Iterator<TransportOption> iterator = enabledTransports.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import org.greenrobot.eventbus.Subscribe;
|
|||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
||||||
@@ -75,16 +76,19 @@ import org.thoughtcrime.securesms.util.FullscreenHelper;
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThrottledDebouncer;
|
import org.thoughtcrime.securesms.util.ThrottledDebouncer;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.VibrateUtil;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState;
|
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState;
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||||
|
|
||||||
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
|
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
|
||||||
@@ -92,6 +96,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
private static final String TAG = Log.tag(WebRtcCallActivity.class);
|
private static final String TAG = Log.tag(WebRtcCallActivity.class);
|
||||||
|
|
||||||
private static final int STANDARD_DELAY_FINISH = 1000;
|
private static final int STANDARD_DELAY_FINISH = 1000;
|
||||||
|
private static final int VIBRATE_DURATION = 50;
|
||||||
|
|
||||||
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
|
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
|
||||||
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
|
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
|
||||||
@@ -111,6 +116,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
||||||
private ThrottledDebouncer requestNewSizesThrottle;
|
private ThrottledDebouncer requestNewSizesThrottle;
|
||||||
|
|
||||||
|
private Disposable ephemeralStateDisposable = Disposable.empty();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(@NonNull Context newBase) {
|
protected void attachBaseContext(@NonNull Context newBase) {
|
||||||
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||||
@@ -153,6 +160,18 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
|
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
ephemeralStateDisposable = ApplicationDependencies.getSignalCallManager()
|
||||||
|
.ephemeralStates()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(state -> {
|
||||||
|
viewModel.updateFromEphemeralState(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
Log.i(TAG, "onResume()");
|
Log.i(TAG, "onResume()");
|
||||||
@@ -193,6 +212,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
Log.i(TAG, "onStop");
|
Log.i(TAG, "onStop");
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
|
ephemeralStateDisposable.dispose();
|
||||||
|
|
||||||
if (!isInPipMode() || isFinishing()) {
|
if (!isInPipMode() || isFinishing()) {
|
||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
requestNewSizesThrottle.clear();
|
requestNewSizesThrottle.clear();
|
||||||
@@ -295,7 +316,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
|
|
||||||
LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(),
|
LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(),
|
||||||
viewModel.getOrientationAndLandscapeEnabled(),
|
viewModel.getOrientationAndLandscapeEnabled(),
|
||||||
(s, o) -> new CallParticipantsViewState(s, o.first == PORTRAIT_BOTTOM_EDGE, o.second))
|
viewModel.getEphemeralState(),
|
||||||
|
(s, o, e) -> new CallParticipantsViewState(s, e, o.first == PORTRAIT_BOTTOM_EDGE, o.second))
|
||||||
.observe(this, p -> callScreen.updateCallParticipants(p));
|
.observe(this, p -> callScreen.updateCallParticipants(p));
|
||||||
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
|
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
|
||||||
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
|
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
|
||||||
@@ -501,6 +523,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCallReconnecting() {
|
||||||
|
callScreen.setStatus(getString(R.string.WebRtcCallActivity__reconnecting));
|
||||||
|
VibrateUtil.vibrate(this, VIBRATE_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleRecipientUnavailable() {
|
private void handleRecipientUnavailable() {
|
||||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable));
|
callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable));
|
||||||
@@ -623,6 +650,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
handleCallPreJoin(event); break;
|
handleCallPreJoin(event); break;
|
||||||
case CALL_CONNECTED:
|
case CALL_CONNECTED:
|
||||||
handleCallConnected(event); break;
|
handleCallConnected(event); break;
|
||||||
|
case CALL_RECONNECTING:
|
||||||
|
handleCallReconnecting(); break;
|
||||||
case NETWORK_FAILURE:
|
case NETWORK_FAILURE:
|
||||||
handleServerFailure(); break;
|
handleServerFailure(); break;
|
||||||
case CALL_RINGING:
|
case CALL_RINGING:
|
||||||
@@ -807,7 +836,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
if (feature.isPresent()) {
|
if (feature.isPresent()) {
|
||||||
FoldingFeature foldingFeature = (FoldingFeature) feature.get();
|
FoldingFeature foldingFeature = (FoldingFeature) feature.get();
|
||||||
Rect bounds = foldingFeature.getBounds();
|
Rect bounds = foldingFeature.getBounds();
|
||||||
if (foldingFeature.getState() == FoldingFeature.State.HALF_OPENED && bounds.top == bounds.bottom) {
|
if (foldingFeature.isSeparating()) {
|
||||||
Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode");
|
Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode");
|
||||||
viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
|
viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.transition.Transition
|
||||||
|
import android.transition.TransitionValues
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.animation.doOnEnd
|
||||||
|
import androidx.core.animation.doOnStart
|
||||||
|
|
||||||
|
@RequiresApi(21)
|
||||||
|
class CrossfaderTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val WIDTH = "CrossfaderTransition.WIDTH"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureStartValues(transitionValues: TransitionValues) {
|
||||||
|
if (transitionValues.view is Crossfadeable) {
|
||||||
|
transitionValues.values[WIDTH] = transitionValues.view.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureEndValues(transitionValues: TransitionValues) {
|
||||||
|
if (transitionValues.view is Crossfadeable) {
|
||||||
|
transitionValues.values[WIDTH] = transitionValues.view.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
|
||||||
|
if (startValues == null || endValues == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val startWidth = (startValues.values[WIDTH] ?: 0) as Int
|
||||||
|
val endWidth = (endValues.values[WIDTH] ?: 0) as Int
|
||||||
|
val view: Crossfadeable = endValues.view as? Crossfadeable ?: return null
|
||||||
|
val reverse = startWidth > endWidth
|
||||||
|
|
||||||
|
return ValueAnimator.ofFloat(0f, 1f).apply {
|
||||||
|
addUpdateListener {
|
||||||
|
view.onCrossfadeAnimationUpdated(it.animatedValue as Float, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
doOnStart {
|
||||||
|
view.onCrossfadeStarted(reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
doOnEnd {
|
||||||
|
view.onCrossfadeFinished(reverse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Crossfadeable {
|
||||||
|
fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean)
|
||||||
|
fun onCrossfadeStarted(reverse: Boolean)
|
||||||
|
fun onCrossfadeFinished(reverse: Boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.transition.Transition
|
||||||
|
import android.transition.TransitionValues
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
|
||||||
|
@RequiresApi(21)
|
||||||
|
class FabElevationFadeTransform(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ELEVATION = "CrossfaderTransition.ELEVATION"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureStartValues(transitionValues: TransitionValues) {
|
||||||
|
if (transitionValues.view is FloatingActionButton) {
|
||||||
|
transitionValues.values[ELEVATION] = transitionValues.view.elevation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureEndValues(transitionValues: TransitionValues) {
|
||||||
|
if (transitionValues.view is FloatingActionButton) {
|
||||||
|
transitionValues.values[ELEVATION] = transitionValues.view.elevation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
|
||||||
|
if (startValues?.view !is FloatingActionButton || endValues?.view !is FloatingActionButton) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val startElevation = startValues.view.elevation
|
||||||
|
val endElevation = endValues.view.elevation
|
||||||
|
if (startElevation == endElevation) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValueAnimator.ofFloat(
|
||||||
|
startValues.values[ELEVATION] as Float,
|
||||||
|
endValues.values[ELEVATION] as Float
|
||||||
|
).apply {
|
||||||
|
addUpdateListener {
|
||||||
|
val elevation = it.animatedValue as Float
|
||||||
|
endValues.view.elevation = elevation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,12 @@ import org.thoughtcrime.securesms.blurhash.BlurHash;
|
|||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class PointerAttachment extends Attachment {
|
public class PointerAttachment extends Attachment {
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) {
|
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) {
|
||||||
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.absent();
|
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.empty();
|
||||||
|
|
||||||
String encodedKey = null;
|
String encodedKey = null;
|
||||||
|
|
||||||
@@ -103,12 +103,12 @@ public class PointerAttachment extends Attachment {
|
|||||||
|
|
||||||
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
||||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||||
pointer.get().asPointer().getSize().or(0),
|
pointer.get().asPointer().getSize().orElse(0),
|
||||||
pointer.get().asPointer().getFileName().orNull(),
|
pointer.get().asPointer().getFileName().orElse(null),
|
||||||
pointer.get().asPointer().getCdnNumber(),
|
pointer.get().asPointer().getCdnNumber(),
|
||||||
pointer.get().asPointer().getRemoteId().toString(),
|
pointer.get().asPointer().getRemoteId().toString(),
|
||||||
encodedKey, null,
|
encodedKey, null,
|
||||||
pointer.get().asPointer().getDigest().orNull(),
|
pointer.get().asPointer().getDigest().orElse(null),
|
||||||
fastPreflightId,
|
fastPreflightId,
|
||||||
pointer.get().asPointer().getVoiceNote(),
|
pointer.get().asPointer().getVoiceNote(),
|
||||||
pointer.get().asPointer().isBorderless(),
|
pointer.get().asPointer().isBorderless(),
|
||||||
@@ -116,9 +116,9 @@ public class PointerAttachment extends Attachment {
|
|||||||
pointer.get().asPointer().getWidth(),
|
pointer.get().asPointer().getWidth(),
|
||||||
pointer.get().asPointer().getHeight(),
|
pointer.get().asPointer().getHeight(),
|
||||||
pointer.get().asPointer().getUploadTimestamp(),
|
pointer.get().asPointer().getUploadTimestamp(),
|
||||||
pointer.get().asPointer().getCaption().orNull(),
|
pointer.get().asPointer().getCaption().orElse(null),
|
||||||
stickerLocator,
|
stickerLocator,
|
||||||
BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orNull())));
|
BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orElse(null))));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,13 +127,13 @@ public class PointerAttachment extends Attachment {
|
|||||||
|
|
||||||
return Optional.of(new PointerAttachment(pointer.getContentType(),
|
return Optional.of(new PointerAttachment(pointer.getContentType(),
|
||||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||||
thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0,
|
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
|
||||||
pointer.getFileName(),
|
pointer.getFileName(),
|
||||||
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
||||||
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
|
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
|
||||||
null,
|
null,
|
||||||
thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null,
|
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
@@ -141,7 +141,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null,
|
thumbnail != null ? thumbnail.asPointer().getCaption().orElse(null) : null,
|
||||||
null,
|
null,
|
||||||
null));
|
null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import org.thoughtcrime.securesms.mms.PartAuthority
|
|||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
import org.thoughtcrime.securesms.util.MediaUtil
|
||||||
import org.whispersystems.libsignal.util.guava.Optional
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.Optional
|
||||||
import javax.annotation.meta.Exhaustive
|
import javax.annotation.meta.Exhaustive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,6 +128,6 @@ object AvatarRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createMedia(uri: Uri, size: Long): Media {
|
private fun createMedia(uri: Uri, size: Long): Media {
|
||||||
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent())
|
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ class TextAvatarDrawable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val textSize = Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f)
|
|
||||||
val width = bounds.width()
|
val width = bounds.width()
|
||||||
|
val textSize = Avatars.getTextSizeForLength(context, avatar.text, width * 0.8f, width * 0.45f)
|
||||||
val candidates = EmojiProvider.getCandidates(avatar.text)
|
val candidates = EmojiProvider.getCandidates(avatar.text)
|
||||||
|
|
||||||
textPaint.textSize = textSize
|
textPaint.textSize = textSize
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ class AvatarView @JvmOverloads constructor(
|
|||||||
isClickable = false
|
isClickable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val avatar: AvatarImageView = findViewById(R.id.avatar_image_view)
|
private val avatar: AvatarImageView = findViewById<AvatarImageView>(R.id.avatar_image_view).apply {
|
||||||
|
initialize(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
private val storyRing: View = findViewById(R.id.avatar_story_ring)
|
private val storyRing: View = findViewById(R.id.avatar_story_ring)
|
||||||
|
|
||||||
private fun showStoryRing(hasUnreadStory: Boolean) {
|
private fun showStoryRing(hasUnreadStory: Boolean) {
|
||||||
@@ -37,8 +40,8 @@ class AvatarView @JvmOverloads constructor(
|
|||||||
storyRing.visible = true
|
storyRing.visible = true
|
||||||
storyRing.isActivated = hasUnreadStory
|
storyRing.isActivated = hasUnreadStory
|
||||||
|
|
||||||
avatar.scaleX = 0.82f
|
avatar.scaleX = 0.8f
|
||||||
avatar.scaleY = 0.82f
|
avatar.scaleY = 0.8f
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideStoryRing() {
|
private fun hideStoryRing() {
|
||||||
@@ -48,6 +51,10 @@ class AvatarView @JvmOverloads constructor(
|
|||||||
avatar.scaleY = 1f
|
avatar.scaleY = 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasStory(): Boolean {
|
||||||
|
return storyRing.visible
|
||||||
|
}
|
||||||
|
|
||||||
fun setStoryRingFromState(storyViewState: StoryViewState) {
|
fun setStoryRingFromState(storyViewState: StoryViewState) {
|
||||||
when (storyViewState) {
|
when (storyViewState) {
|
||||||
StoryViewState.NONE -> hideStoryRing()
|
StoryViewState.NONE -> hideStoryRing()
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import androidx.annotation.RequiresApi;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
@@ -110,7 +112,7 @@ public class BackupDialog {
|
|||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
public static void showChooseBackupLocationDialog(@NonNull Fragment fragment, int requestCode) {
|
public static void showChooseBackupLocationDialog(@NonNull Fragment fragment, int requestCode) {
|
||||||
new AlertDialog.Builder(fragment.requireContext())
|
new MaterialAlertDialogBuilder(fragment.requireContext())
|
||||||
.setView(R.layout.backup_choose_location_dialog)
|
.setView(R.layout.backup_choose_location_dialog)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||||
@@ -141,7 +143,7 @@ public class BackupDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void showDisableBackupDialog(@NonNull Context context, @NonNull Runnable onBackupsDisabled) {
|
public static void showDisableBackupDialog(@NonNull Context context, @NonNull Runnable onBackupsDisabled) {
|
||||||
new AlertDialog.Builder(context)
|
new MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(R.string.BackupDialog_delete_backups)
|
.setTitle(R.string.BackupDialog_delete_backups)
|
||||||
.setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups)
|
.setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups)
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||||
import org.whispersystems.libsignal.util.ByteUtil;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
|||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.core.util.Conversions;
|
import org.signal.core.util.Conversions;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.libsignal.protocol.kdf.HKDFv3;
|
||||||
|
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
||||||
@@ -47,13 +48,11 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
import org.signal.core.util.CursorUtil;
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
import org.signal.core.util.SetUtil;
|
||||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
|
||||||
import org.whispersystems.libsignal.util.ByteUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import org.greenrobot.eventbus.EventBus;
|
|||||||
import org.signal.core.util.Conversions;
|
import org.signal.core.util.Conversions;
|
||||||
import org.signal.core.util.StreamUtil;
|
import org.signal.core.util.StreamUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.libsignal.protocol.kdf.HKDFv3;
|
||||||
|
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||||
import org.thoughtcrime.securesms.backup.BackupProtos.Attachment;
|
import org.thoughtcrime.securesms.backup.BackupProtos.Attachment;
|
||||||
import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame;
|
import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame;
|
||||||
import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion;
|
import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion;
|
||||||
@@ -36,9 +38,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
import org.signal.core.util.SqlUtil;
|
||||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
|
||||||
import org.whispersystems.libsignal.util.ByteUtil;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -69,6 +69,18 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(FullBackupImporter.class);
|
private static final String TAG = Log.tag(FullBackupImporter.class);
|
||||||
|
|
||||||
|
private static final String[] TABLES_TO_DROP_FIRST = {
|
||||||
|
"distribution_list_member",
|
||||||
|
"distribution_list",
|
||||||
|
"message_send_log_recipients",
|
||||||
|
"msl_recipient",
|
||||||
|
"msl_message",
|
||||||
|
"reaction",
|
||||||
|
"notification_profile_schedule",
|
||||||
|
"notification_profile_allowed_members",
|
||||||
|
"story_sends"
|
||||||
|
};
|
||||||
|
|
||||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||||
throws IOException
|
throws IOException
|
||||||
@@ -85,12 +97,12 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
|
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
|
||||||
|
|
||||||
|
db.beginTransaction();
|
||||||
|
keyValueDatabase.beginTransaction();
|
||||||
try {
|
try {
|
||||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
keyValueDatabase.beginTransaction();
|
|
||||||
|
|
||||||
dropAllTables(db);
|
dropAllTables(db);
|
||||||
|
|
||||||
BackupFrame frame;
|
BackupFrame frame;
|
||||||
@@ -272,12 +284,17 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
||||||
|
for (String name : TABLES_TO_DROP_FIRST) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + name);
|
||||||
|
}
|
||||||
|
|
||||||
try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) {
|
try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) {
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
String name = cursor.getString(0);
|
String name = cursor.getString(0);
|
||||||
String type = cursor.getString(1);
|
String type = cursor.getString(1);
|
||||||
|
|
||||||
if ("table".equals(type) && !name.startsWith("sqlite_")) {
|
if ("table".equals(type) && !name.startsWith("sqlite_")) {
|
||||||
|
Log.i(TAG, "Dropping table: " + name);
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + name);
|
db.execSQL("DROP TABLE IF EXISTS " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.mms.GlideApp
|
|||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
|
|
||||||
class BadgeImageView @JvmOverloads constructor(
|
class BadgeImageView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import com.google.android.flexbox.FlexboxLayoutManager
|
|||||||
import com.google.android.flexbox.JustifyContent
|
import com.google.android.flexbox.JustifyContent
|
||||||
import org.signal.core.util.DimensionUnit
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.libsignal.protocol.util.Pair
|
||||||
import org.thoughtcrime.securesms.BuildConfig
|
import org.thoughtcrime.securesms.BuildConfig
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge.Category.Companion.fromCode
|
import org.thoughtcrime.securesms.badges.models.Badge.Category.Companion.fromCode
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
|
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
|
||||||
import org.thoughtcrime.securesms.util.ScreenDensity
|
import org.thoughtcrime.securesms.util.ScreenDensity
|
||||||
import org.whispersystems.libsignal.util.Pair
|
|
||||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.sql.Timestamp
|
import java.sql.Timestamp
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class CantProcessSubscriptionPaymentBottomSheetDialogFragment : DSLSettingsBotto
|
|||||||
text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again)
|
text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again)
|
||||||
) {
|
) {
|
||||||
SignalStore.donationsValues().showCantProcessDialog = false
|
SignalStore.donationsValues().showCantProcessDialog = false
|
||||||
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
|||||||
sectionHeaderPref(
|
sectionHeaderPref(
|
||||||
DSLSettingsText.from(
|
DSLSettingsText.from(
|
||||||
if (badge.isBoost()) {
|
if (badge.isBoost()) {
|
||||||
R.string.ExpiredBadgeBottomSheetDialogFragment__your_badge_has_expired
|
R.string.ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired
|
||||||
} else {
|
} else {
|
||||||
R.string.ExpiredBadgeBottomSheetDialogFragment__subscription_cancelled
|
R.string.ExpiredBadgeBottomSheetDialogFragment__monthly_donation_cancelled
|
||||||
},
|
},
|
||||||
DSLSettingsText.CenterModifier
|
DSLSettingsText.CenterModifier
|
||||||
)
|
)
|
||||||
@@ -54,11 +54,11 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
|||||||
noPadTextPref(
|
noPadTextPref(
|
||||||
DSLSettingsText.from(
|
DSLSettingsText.from(
|
||||||
if (badge.isBoost()) {
|
if (badge.isBoost()) {
|
||||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired)
|
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired_and)
|
||||||
} else if (inactive) {
|
} else if (inactive) {
|
||||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_automatically, badge.name)
|
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_canceled)
|
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled)
|
||||||
},
|
},
|
||||||
DSLSettingsText.CenterModifier
|
DSLSettingsText.CenterModifier
|
||||||
)
|
)
|
||||||
@@ -72,7 +72,7 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
|||||||
if (isLikelyASustainer) {
|
if (isLikelyASustainer) {
|
||||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
|
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
|
||||||
} else {
|
} else {
|
||||||
R.string.ExpiredBadgeBottomSheetDialogFragment__to_continue_supporting_technology
|
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_keep
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can
|
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
import org.whispersystems.libsignal.util.guava.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
private val TAG = Log.tag(BadgesOverviewViewModel::class.java)
|
private val TAG = Log.tag(BadgesOverviewViewModel::class.java)
|
||||||
|
|
||||||
@@ -54,13 +54,13 @@ class BadgesOverviewViewModel(
|
|||||||
subscriptionsRepository.getSubscriptions()
|
subscriptionsRepository.getSubscriptions()
|
||||||
) { active, all ->
|
) { active, all ->
|
||||||
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) {
|
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) {
|
||||||
Optional.fromNullable<String>(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id)
|
Optional.ofNullable<String>(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id)
|
||||||
} else {
|
} else {
|
||||||
Optional.absent()
|
Optional.empty()
|
||||||
}
|
}
|
||||||
}.subscribeBy(
|
}.subscribeBy(
|
||||||
onSuccess = { badgeId ->
|
onSuccess = { badgeId ->
|
||||||
store.update { it.copy(fadedBadgeId = badgeId.orNull()) }
|
store.update { it.copy(fadedBadgeId = badgeId.orElse(null)) }
|
||||||
},
|
},
|
||||||
onError = { throwable ->
|
onError = { throwable ->
|
||||||
Log.w(TAG, "Could not retrieve data from server", throwable)
|
Log.w(TAG, "Could not retrieve data from server", throwable)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
|
||||||
import org.thoughtcrime.securesms.badges.BadgeRepository
|
import org.thoughtcrime.securesms.badges.BadgeRepository
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener {
|
public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener {
|
||||||
@@ -88,7 +88,7 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
|
public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
|
||||||
final String displayName = recipientId.transform(id -> Recipient.resolved(id).getDisplayName(this)).or(number);
|
final String displayName = recipientId.map(id -> Recipient.resolved(id).getDisplayName(this)).orElse(number);
|
||||||
|
|
||||||
AlertDialog confirmationDialog = new MaterialAlertDialogBuilder(this)
|
AlertDialog confirmationDialog = new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.BlockedUsersActivity__block_user)
|
.setTitle(R.string.BlockedUsersActivity__block_user)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -64,7 +65,9 @@ final class BlockedUsersAdapter extends ListAdapter<Recipient, BlockedUsersAdapt
|
|||||||
displayName.setText(recipient.getDisplayName(itemView.getContext()));
|
displayName.setText(recipient.getDisplayName(itemView.getContext()));
|
||||||
|
|
||||||
if (recipient.hasAUserSetDisplayName(itemView.getContext())) {
|
if (recipient.hasAUserSetDisplayName(itemView.getContext())) {
|
||||||
String identifier = recipient.getE164().transform(PhoneNumberFormatter::prettyPrint).or(recipient.getUsername()).orNull();
|
String identifier = OptionalUtil.or(recipient.getE164().map(PhoneNumberFormatter::prettyPrint),
|
||||||
|
recipient.getUsername())
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
if (identifier != null) {
|
if (identifier != null) {
|
||||||
numberOrUsername.setText(identifier);
|
numberOrUsername.setText(identifier);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
initialize(context, attrs);
|
initialize(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
|
public void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
setScaleType(ScaleType.CENTER_CROP);
|
setScaleType(ScaleType.CENTER_CROP);
|
||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
|
|||||||
import org.thoughtcrime.securesms.database.model.Mention;
|
import org.thoughtcrime.securesms.database.model.Mention;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.StringUtil;
|
import org.signal.core.util.StringUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
|||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ConversationItemFooter extends ConstraintLayout {
|
public class ConversationItemFooter extends ConstraintLayout {
|
||||||
|
|||||||
@@ -11,15 +11,11 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ConversationTypingView extends ConstraintLayout {
|
public class ConversationTypingView extends ConstraintLayout {
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import android.graphics.Path;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class CornerMask {
|
public class CornerMask {
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.events.PartProgressEvent;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
|
||||||
public class DocumentView extends FrameLayout {
|
public class DocumentView extends FrameLayout {
|
||||||
|
|
||||||
@@ -105,11 +106,11 @@ public class DocumentView extends FrameLayout {
|
|||||||
|
|
||||||
this.documentSlide = documentSlide;
|
this.documentSlide = documentSlide;
|
||||||
|
|
||||||
this.fileName.setText(documentSlide.getFileName()
|
this.fileName.setText(OptionalUtil.or(documentSlide.getFileName(),
|
||||||
.or(documentSlide.getCaption())
|
documentSlide.getCaption())
|
||||||
.or(getContext().getString(R.string.DocumentView_unnamed_file)));
|
.orElse(getContext().getString(R.string.DocumentView_unnamed_file)));
|
||||||
this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize()));
|
this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize()));
|
||||||
this.document.setText(documentSlide.getFileType(getContext()).or("").toLowerCase());
|
this.document.setText(documentSlide.getFileType(getContext()).orElse("").toLowerCase());
|
||||||
this.setOnClickListener(new OpenClickedListener(documentSlide));
|
this.setOnClickListener(new OpenClickedListener(documentSlide));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.thoughtcrime.securesms.components
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
@@ -26,6 +28,9 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
|
|||||||
@StyleRes
|
@StyleRes
|
||||||
protected open val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners
|
protected open val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
protected var backgroundColor: Int = Color.TRANSPARENT
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setStyle(STYLE_NORMAL, themeResId)
|
setStyle(STYLE_NORMAL, themeResId)
|
||||||
@@ -44,7 +49,8 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
|
|||||||
val dialogBackground = MaterialShapeDrawable(shapeAppearanceModel)
|
val dialogBackground = MaterialShapeDrawable(shapeAppearanceModel)
|
||||||
|
|
||||||
val bottomSheetStyle = ThemeUtil.getThemedResourceId(ContextThemeWrapper(requireContext(), themeResId), R.attr.bottomSheetStyle)
|
val bottomSheetStyle = ThemeUtil.getThemedResourceId(ContextThemeWrapper(requireContext(), themeResId), R.attr.bottomSheetStyle)
|
||||||
dialogBackground.setTint(ThemeUtil.getThemedColor(ContextThemeWrapper(requireContext(), bottomSheetStyle), R.attr.backgroundTint))
|
backgroundColor = ThemeUtil.getThemedColor(ContextThemeWrapper(requireContext(), bottomSheetStyle), R.attr.backgroundTint)
|
||||||
|
dialogBackground.setTint(backgroundColor)
|
||||||
|
|
||||||
dialog.behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
dialog.behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class FromTextView extends SimpleEmojiTextView {
|
|||||||
builder.append(suffix);
|
builder.append(suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipient.isReleaseNotes()) {
|
if (recipient.showVerified()) {
|
||||||
Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_20);
|
Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_20);
|
||||||
official.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20));
|
official.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20));
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base dialog fragment for rendering as a full screen dialog with animation
|
* Base dialog fragment for rendering as a full screen dialog with animation
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class InputPanel extends LinearLayout
|
public class InputPanel extends LinearLayout
|
||||||
@@ -181,13 +181,19 @@ public class InputPanel extends LinearLayout
|
|||||||
@NonNull CharSequence body,
|
@NonNull CharSequence body,
|
||||||
@NonNull SlideDeck attachments)
|
@NonNull SlideDeck attachments)
|
||||||
{
|
{
|
||||||
this.quoteView.setQuote(glideRequests, id, author, body, false, attachments, null);
|
this.quoteView.setQuote(glideRequests, id, author, body, false, attachments, null, null);
|
||||||
|
|
||||||
int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight()
|
int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight()
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
this.quoteView.setVisibility(VISIBLE);
|
this.quoteView.setVisibility(VISIBLE);
|
||||||
this.quoteView.measure(0, 0);
|
|
||||||
|
int maxWidth = composeContainer.getWidth();
|
||||||
|
if (quoteView.getLayoutParams() instanceof MarginLayoutParams) {
|
||||||
|
MarginLayoutParams layoutParams = (MarginLayoutParams) quoteView.getLayoutParams();
|
||||||
|
maxWidth -= layoutParams.leftMargin + layoutParams.rightMargin;
|
||||||
|
}
|
||||||
|
this.quoteView.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), 0);
|
||||||
|
|
||||||
if (quoteAnimator != null) {
|
if (quoteAnimator != null) {
|
||||||
quoteAnimator.cancel();
|
quoteAnimator.cancel();
|
||||||
@@ -252,7 +258,7 @@ public class InputPanel extends LinearLayout
|
|||||||
if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) {
|
if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) {
|
||||||
return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody().toString(), false, quoteView.getAttachments(), quoteView.getMentions()));
|
return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody().toString(), false, quoteView.getAttachments(), quoteView.getMentions()));
|
||||||
} else {
|
} else {
|
||||||
return Optional.absent();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.constraintlayout.widget.Guideline;
|
import androidx.constraintlayout.widget.Guideline;
|
||||||
|
|
||||||
import org.signal.glide.Log;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ abstract class KeyboardEntryDialogFragment(@LayoutRes contentLayoutId: Int) :
|
|||||||
dialog.window?.setDimAmount(0f)
|
dialog.window?.setDimAmount(0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view shown in the compose box or conversation that represents the state of the link preview.
|
* The view shown in the compose box or conversation that represents the state of the link preview.
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import android.widget.ImageView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
@@ -101,7 +100,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
|||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
|
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
|
||||||
if (listener != null) listener.onRecordPermissionRequired();
|
if (listener != null) listener.onRecordPermissionRequired();
|
||||||
} else {
|
} else if (state == State.NOT_RUNNING) {
|
||||||
state = State.RUNNING_HELD;
|
state = State.RUNNING_HELD;
|
||||||
floatingRecordButton.display(event.getX(), event.getY());
|
floatingRecordButton.display(event.getX(), event.getY());
|
||||||
lockDropTarget.display();
|
lockDropTarget.display();
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
|
|
||||||
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panel component combining both an editable field with a button for
|
|
||||||
* a list-based contact selector.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
public class PushRecipientsPanel extends RelativeLayout implements RecipientForeverObserver {
|
|
||||||
private final String TAG = Log.tag(PushRecipientsPanel.class);
|
|
||||||
private RecipientsPanelChangedListener panelChangeListener;
|
|
||||||
|
|
||||||
private RecipientsEditor recipientsText;
|
|
||||||
private View panel;
|
|
||||||
|
|
||||||
private static final int RECIPIENTS_MAX_LENGTH = 312;
|
|
||||||
|
|
||||||
public PushRecipientsPanel(Context context) {
|
|
||||||
super(context);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PushRecipientsPanel(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PushRecipientsPanel(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow() {
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
Stream.of(getRecipients()).map(Recipient::live).forEach(r -> r.removeForeverObserver(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Recipient> getRecipients() {
|
|
||||||
String rawText = recipientsText.getText().toString();
|
|
||||||
return getRecipientsFromString(getContext(), rawText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disable() {
|
|
||||||
recipientsText.setText("");
|
|
||||||
panel.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPanelChangeListener(RecipientsPanelChangedListener panelChangeListener) {
|
|
||||||
this.panelChangeListener = panelChangeListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize() {
|
|
||||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
inflater.inflate(R.layout.push_recipients_panel, this, true);
|
|
||||||
|
|
||||||
View imageButton = findViewById(R.id.contacts_button);
|
|
||||||
((MarginLayoutParams) imageButton.getLayoutParams()).topMargin = 0;
|
|
||||||
|
|
||||||
panel = findViewById(R.id.recipients_panel);
|
|
||||||
initRecipientsEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRecipientsEditor() {
|
|
||||||
|
|
||||||
this.recipientsText = (RecipientsEditor)findViewById(R.id.recipients_text);
|
|
||||||
|
|
||||||
List<Recipient> recipients = getRecipients();
|
|
||||||
|
|
||||||
Stream.of(recipients).map(Recipient::live).forEach(r -> r.observeForever(this));
|
|
||||||
|
|
||||||
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
|
|
||||||
recipientsText.populate(recipients);
|
|
||||||
|
|
||||||
recipientsText.setOnFocusChangeListener(new FocusChangedListener());
|
|
||||||
recipientsText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
|
||||||
if (panelChangeListener != null) {
|
|
||||||
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
|
|
||||||
}
|
|
||||||
recipientsText.setText("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NonNull List<Recipient> getRecipientsFromString(Context context, @NonNull String rawText) {
|
|
||||||
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
|
|
||||||
List<Recipient> recipients = new LinkedList<>();
|
|
||||||
|
|
||||||
while (tokenizer.hasMoreTokens()) {
|
|
||||||
String token = tokenizer.nextToken().trim();
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(token)) {
|
|
||||||
if (hasBracketedNumber(token)) recipients.add(Recipient.external(context, parseBracketedNumber(token)));
|
|
||||||
else recipients.add(Recipient.external(context, token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasBracketedNumber(String recipient) {
|
|
||||||
int openBracketIndex = recipient.indexOf('<');
|
|
||||||
|
|
||||||
return (openBracketIndex != -1) &&
|
|
||||||
(recipient.indexOf('>', openBracketIndex) != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseBracketedNumber(String recipient) {
|
|
||||||
int begin = recipient.indexOf('<');
|
|
||||||
int end = recipient.indexOf('>', begin);
|
|
||||||
String value = recipient.substring(begin + 1, end);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRecipientChanged(@NonNull Recipient recipient) {
|
|
||||||
recipientsText.populate(getRecipients());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FocusChangedListener implements View.OnFocusChangeListener {
|
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
|
||||||
if (!hasFocus && (panelChangeListener != null)) {
|
|
||||||
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface RecipientsPanelChangedListener {
|
|
||||||
public void onRecipientsPanelUpdate(List<Recipient> recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||||
import org.thoughtcrime.securesms.database.model.Mention;
|
import org.thoughtcrime.securesms.database.model.Mention;
|
||||||
@@ -34,10 +35,13 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
|
|||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||||
|
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.Projection;
|
import org.thoughtcrime.securesms.util.Projection;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||||
@@ -49,7 +53,9 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
PREVIEW(0),
|
PREVIEW(0),
|
||||||
OUTGOING(1),
|
OUTGOING(1),
|
||||||
INCOMING(2),
|
INCOMING(2),
|
||||||
STORY_REPLY(3);
|
STORY_REPLY_OUTGOING(3),
|
||||||
|
STORY_REPLY_INCOMING(4),
|
||||||
|
STORY_REPLY_PREVIEW(5);
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
@@ -68,16 +74,18 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewGroup mainView;
|
private ViewGroup mainView;
|
||||||
private ViewGroup footerView;
|
private ViewGroup footerView;
|
||||||
private TextView authorView;
|
private TextView authorView;
|
||||||
private TextView bodyView;
|
private TextView bodyView;
|
||||||
private View quoteBarView;
|
private View quoteBarView;
|
||||||
private ImageView thumbnailView;
|
private ImageView thumbnailView;
|
||||||
private View attachmentVideoOverlayView;
|
private View attachmentVideoOverlayView;
|
||||||
private ViewGroup attachmentContainerView;
|
private ViewGroup attachmentContainerView;
|
||||||
private TextView attachmentNameView;
|
private TextView attachmentNameView;
|
||||||
private ImageView dismissView;
|
private ImageView dismissView;
|
||||||
|
private EmojiImageView missingStoryReaction;
|
||||||
|
private EmojiImageView storyReactionEmoji;
|
||||||
|
|
||||||
private long id;
|
private long id;
|
||||||
private LiveRecipient author;
|
private LiveRecipient author;
|
||||||
@@ -129,6 +137,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
this.dismissView = findViewById(R.id.quote_dismiss);
|
this.dismissView = findViewById(R.id.quote_dismiss);
|
||||||
this.mediaDescriptionText = findViewById(R.id.media_type);
|
this.mediaDescriptionText = findViewById(R.id.media_type);
|
||||||
this.missingLinkText = findViewById(R.id.quote_missing_text);
|
this.missingLinkText = findViewById(R.id.quote_missing_text);
|
||||||
|
this.missingStoryReaction = findViewById(R.id.quote_missing_story_reaction_emoji);
|
||||||
|
this.storyReactionEmoji = findViewById(R.id.quote_story_reaction_emoji);
|
||||||
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_large);
|
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_large);
|
||||||
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
|
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
|
||||||
|
|
||||||
@@ -177,13 +187,11 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
|
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
|
||||||
cornerMask.setTopLeftRadius(radius);
|
cornerMask.setTopLeftRadius(radius);
|
||||||
cornerMask.setTopRightRadius(radius);
|
cornerMask.setTopRightRadius(radius);
|
||||||
} else if (messageType == MessageType.STORY_REPLY) {
|
} else if (isStoryReply()) {
|
||||||
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
|
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
|
||||||
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
|
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainView.setMinimumHeight(thumbHeight);
|
|
||||||
|
|
||||||
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
|
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
|
||||||
params.height = thumbHeight;
|
params.height = thumbHeight;
|
||||||
params.width = thumbWidth;
|
params.width = thumbWidth;
|
||||||
@@ -197,7 +205,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
@Nullable CharSequence body,
|
@Nullable CharSequence body,
|
||||||
boolean originalMissing,
|
boolean originalMissing,
|
||||||
@NonNull SlideDeck attachments,
|
@NonNull SlideDeck attachments,
|
||||||
@Nullable ChatColors chatColors)
|
@Nullable ChatColors chatColors,
|
||||||
|
@Nullable String storyReaction)
|
||||||
{
|
{
|
||||||
if (this.author != null) this.author.removeForeverObserver(this);
|
if (this.author != null) this.author.removeForeverObserver(this);
|
||||||
|
|
||||||
@@ -208,8 +217,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
|
|
||||||
this.author.observeForever(this);
|
this.author.observeForever(this);
|
||||||
setQuoteAuthor(author);
|
setQuoteAuthor(author);
|
||||||
setQuoteText(body, attachments);
|
setQuoteText(body, attachments, originalMissing, storyReaction);
|
||||||
setQuoteAttachment(glideRequests, attachments);
|
setQuoteAttachment(glideRequests, body, attachments, originalMissing);
|
||||||
setQuoteMissingFooter(originalMissing);
|
setQuoteMissingFooter(originalMissing);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21 && messageType == MessageType.INCOMING && chatColors != null) {
|
if (Build.VERSION.SDK_INT < 21 && messageType == MessageType.INCOMING && chatColors != null) {
|
||||||
@@ -248,10 +257,10 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setQuoteAuthor(@NonNull Recipient author) {
|
private void setQuoteAuthor(@NonNull Recipient author) {
|
||||||
boolean outgoing = messageType != MessageType.INCOMING;
|
boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING;
|
||||||
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY;
|
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW;
|
||||||
|
|
||||||
if (messageType == MessageType.STORY_REPLY) {
|
if (isStoryReply()) {
|
||||||
authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_your_story)
|
authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_your_story)
|
||||||
: getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
|
: getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
|
||||||
} else {
|
} else {
|
||||||
@@ -259,14 +268,70 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
: author.getDisplayName(getContext()));
|
: author.getDisplayName(getContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing ? R.color.core_white : android.R.color.transparent));
|
quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing || isStoryReply() ? R.color.core_white : android.R.color.transparent));
|
||||||
mainView.setBackgroundColor(ContextCompat.getColor(getContext(), preview ? R.color.quote_preview_background : R.color.quote_view_background));
|
|
||||||
|
int mainViewColor;
|
||||||
|
if (preview) {
|
||||||
|
mainViewColor = R.color.quote_preview_background;
|
||||||
|
} else if (!outgoing && isStoryReply()) {
|
||||||
|
mainViewColor = R.color.quote_incoming_story_background;
|
||||||
|
} else {
|
||||||
|
mainViewColor = R.color.quote_view_background;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainView.setBackgroundColor(ContextCompat.getColor(getContext(), mainViewColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setQuoteText(@Nullable CharSequence body, @NonNull SlideDeck attachments) {
|
private boolean isStoryReply() {
|
||||||
|
return messageType == MessageType.STORY_REPLY_OUTGOING ||
|
||||||
|
messageType == MessageType.STORY_REPLY_INCOMING ||
|
||||||
|
messageType == MessageType.STORY_REPLY_PREVIEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setQuoteText(@Nullable CharSequence body,
|
||||||
|
@NonNull SlideDeck attachments,
|
||||||
|
boolean originalMissing,
|
||||||
|
@Nullable String storyReaction)
|
||||||
|
{
|
||||||
|
if (originalMissing && isStoryReply()) {
|
||||||
|
bodyView.setVisibility(GONE);
|
||||||
|
storyReactionEmoji.setVisibility(View.GONE);
|
||||||
|
mediaDescriptionText.setVisibility(VISIBLE);
|
||||||
|
|
||||||
|
mediaDescriptionText.setText(R.string.QuoteView_no_longer_available);
|
||||||
|
if (storyReaction != null) {
|
||||||
|
missingStoryReaction.setVisibility(View.VISIBLE);
|
||||||
|
missingStoryReaction.setImageEmoji(body);
|
||||||
|
} else {
|
||||||
|
missingStoryReaction.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storyReaction != null) {
|
||||||
|
storyReactionEmoji.setImageEmoji(storyReaction);
|
||||||
|
storyReactionEmoji.setVisibility(View.VISIBLE);
|
||||||
|
missingStoryReaction.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
storyReactionEmoji.setVisibility(View.GONE);
|
||||||
|
missingStoryReaction.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTextStory = !attachments.containsMediaSlide() && isStoryReply();
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) {
|
if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) {
|
||||||
|
if (isTextStory && body != null) {
|
||||||
|
try {
|
||||||
|
bodyView.setText(getStoryTextPost(body).getText());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "Could not parse body of text post.", e);
|
||||||
|
bodyView.setText("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bodyView.setText(body == null ? "" : body);
|
||||||
|
}
|
||||||
|
|
||||||
bodyView.setVisibility(VISIBLE);
|
bodyView.setVisibility(VISIBLE);
|
||||||
bodyView.setText(body == null ? "" : body);
|
|
||||||
mediaDescriptionText.setVisibility(GONE);
|
mediaDescriptionText.setVisibility(GONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -305,7 +370,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
|
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull CharSequence body, @NonNull SlideDeck slideDeck, boolean originalMissing) {
|
||||||
|
mainView.setMinimumHeight(isStoryReply() && originalMissing ? 0 : thumbHeight);
|
||||||
|
|
||||||
|
if (!attachments.containsMediaSlide() && isStoryReply()) {
|
||||||
|
StoryTextPostModel model = getStoryTextPost(body);
|
||||||
|
attachmentVideoOverlayView.setVisibility(GONE);
|
||||||
|
attachmentContainerView.setVisibility(GONE);
|
||||||
|
thumbnailView.setVisibility(VISIBLE);
|
||||||
|
glideRequests.load(model)
|
||||||
|
.centerCrop()
|
||||||
|
.override(thumbWidth, thumbHeight)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
|
.into(thumbnailView);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Slide imageVideoSlide = slideDeck.getSlides().stream().filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).findFirst().orElse(null);
|
Slide imageVideoSlide = slideDeck.getSlides().stream().filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).findFirst().orElse(null);
|
||||||
Slide documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null);
|
Slide documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null);
|
||||||
Slide viewOnceSlide = slideDeck.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null);
|
Slide viewOnceSlide = slideDeck.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null);
|
||||||
@@ -330,7 +410,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
} else if (documentSlide != null){
|
} else if (documentSlide != null){
|
||||||
thumbnailView.setVisibility(GONE);
|
thumbnailView.setVisibility(GONE);
|
||||||
attachmentContainerView.setVisibility(VISIBLE);
|
attachmentContainerView.setVisibility(VISIBLE);
|
||||||
attachmentNameView.setText(documentSlide.getFileName().or(""));
|
attachmentNameView.setText(documentSlide.getFileName().orElse(""));
|
||||||
} else {
|
} else {
|
||||||
thumbnailView.setVisibility(GONE);
|
thumbnailView.setVisibility(GONE);
|
||||||
attachmentContainerView.setVisibility(GONE);
|
attachmentContainerView.setVisibility(GONE);
|
||||||
@@ -343,10 +423,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setQuoteMissingFooter(boolean missing) {
|
private void setQuoteMissingFooter(boolean missing) {
|
||||||
footerView.setVisibility(missing ? VISIBLE : GONE);
|
footerView.setVisibility(missing && !isStoryReply() ? VISIBLE : GONE);
|
||||||
footerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.quote_view_background));
|
footerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.quote_view_background));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable StoryTextPostModel getStoryTextPost(@Nullable CharSequence body) {
|
||||||
|
if (Util.isEmpty(body)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return StoryTextPostModel.parseFrom(body.toString(), id, author.getId());
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setTextSize(int unit, float size) {
|
public void setTextSize(int unit, float size) {
|
||||||
bodyView.setTextSize(unit, size);
|
bodyView.setTextSize(unit, size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class SearchToolbar extends LinearLayout {
|
|||||||
EditText searchText = searchView.findViewById(R.id.search_src_text);
|
EditText searchText = searchView.findViewById(R.id.search_src_text);
|
||||||
|
|
||||||
searchView.setSubmitButtonEnabled(false);
|
searchView.setSubmitButtonEnabled(false);
|
||||||
|
searchView.setMaxWidth(Integer.MAX_VALUE);
|
||||||
|
|
||||||
if (searchText != null) searchText.setHint(R.string.SearchToolbar_search);
|
if (searchText != null) searchText.setHint(R.string.SearchToolbar_search);
|
||||||
else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search));
|
else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search));
|
||||||
@@ -65,7 +66,9 @@ public class SearchToolbar extends LinearLayout {
|
|||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
if (listener != null) listener.onSearchTextChange(query);
|
if (listener != null) {
|
||||||
|
listener.onSearchTextChange(query);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import org.thoughtcrime.securesms.TransportOptions;
|
|||||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||||
import org.thoughtcrime.securesms.TransportOptionsPopup;
|
import org.thoughtcrime.securesms.TransportOptionsPopup;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
public class SendButton extends AppCompatImageButton
|
public class SendButton extends AppCompatImageButton
|
||||||
implements TransportOptions.OnTransportChangedListener,
|
implements TransportOptions.OnTransportChangedListener,
|
||||||
@@ -22,7 +24,7 @@ public class SendButton extends AppCompatImageButton
|
|||||||
|
|
||||||
private final TransportOptions transportOptions;
|
private final TransportOptions transportOptions;
|
||||||
|
|
||||||
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.absent();
|
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.empty();
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public SendButton(Context context) {
|
public SendButton(Context context) {
|
||||||
|
|||||||
@@ -34,16 +34,17 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
|
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
@@ -68,7 +69,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
private final int[] bounds = new int[4];
|
private final int[] bounds = new int[4];
|
||||||
private final int[] measureDimens = new int[2];
|
private final int[] measureDimens = new int[2];
|
||||||
|
|
||||||
private Optional<TransferControlView> transferControls = Optional.absent();
|
private Optional<TransferControlView> transferControls = Optional.empty();
|
||||||
private SlideClickListener thumbnailClickListener = null;
|
private SlideClickListener thumbnailClickListener = null;
|
||||||
private SlidesClickedListener downloadClickListener = null;
|
private SlidesClickedListener downloadClickListener = null;
|
||||||
private Slide slide = null;
|
private Slide slide = null;
|
||||||
@@ -391,6 +392,32 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull StoryTextPostModel model, int width, int height) {
|
||||||
|
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||||
|
|
||||||
|
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||||
|
|
||||||
|
GlideRequest request = glideRequests.load(model)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.placeholder(model.getPlaceholder())
|
||||||
|
.transition(withCrossFade());
|
||||||
|
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
request = request.override(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radius > 0) {
|
||||||
|
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
|
||||||
|
} else {
|
||||||
|
request = request.transforms(new CenterCrop());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.into(new GlideDrawableListeningTarget(image, future));
|
||||||
|
blurhash.setImageDrawable(null);
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
public void setThumbnailClickListener(SlideClickListener listener) {
|
public void setThumbnailClickListener(SlideClickListener listener) {
|
||||||
this.thumbnailClickListener = listener;
|
this.thumbnailClickListener = listener;
|
||||||
}
|
}
|
||||||
@@ -401,11 +428,15 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
public void clear(GlideRequests glideRequests) {
|
public void clear(GlideRequests glideRequests) {
|
||||||
glideRequests.clear(image);
|
glideRequests.clear(image);
|
||||||
|
image.setImageDrawable(null);
|
||||||
|
|
||||||
if (transferControls.isPresent()) {
|
if (transferControls.isPresent()) {
|
||||||
getTransferControls().clear();
|
getTransferControls().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glideRequests.clear(blurhash);
|
||||||
|
blurhash.setImageDrawable(null);
|
||||||
|
|
||||||
slide = null;
|
slide = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ package org.thoughtcrime.securesms.components;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||||
@@ -24,11 +29,12 @@ import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder
|
|||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
|
import org.thoughtcrime.securesms.util.ActionRequestListener;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -79,7 +85,7 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType)
|
public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType, @NonNull Runnable onMediaReady)
|
||||||
{
|
{
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
final int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
||||||
@@ -101,15 +107,16 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
|
|
||||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||||
Log.i(TAG, "Loading in standard image view...");
|
Log.i(TAG, "Loading in standard image view...");
|
||||||
setImageViewUri(glideRequests, uri);
|
setImageViewUri(glideRequests, uri, onMediaReady);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Loading in subsampling image view...");
|
Log.i(TAG, "Loading in subsampling image view...");
|
||||||
setSubsamplingImageViewUri(uri);
|
setSubsamplingImageViewUri(uri);
|
||||||
|
onMediaReady.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull Runnable onMediaReady) {
|
||||||
photoView.setVisibility(View.VISIBLE);
|
photoView.setVisibility(View.VISIBLE);
|
||||||
subsamplingImageView.setVisibility(View.GONE);
|
subsamplingImageView.setVisibility(View.GONE);
|
||||||
|
|
||||||
@@ -117,6 +124,7 @@ public class ZoomingImageView extends FrameLayout {
|
|||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.dontTransform()
|
.dontTransform()
|
||||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||||
|
.addListener(ActionRequestListener.onEither(onMediaReady))
|
||||||
.into(photoView);
|
.into(photoView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class CameraView extends ViewGroup {
|
public class CameraView extends ViewGroup {
|
||||||
@@ -56,8 +56,8 @@ public class CameraView extends ViewGroup {
|
|||||||
private final CameraSurfaceView surface;
|
private final CameraSurfaceView surface;
|
||||||
private final OnOrientationChange onOrientationChange;
|
private final OnOrientationChange onOrientationChange;
|
||||||
|
|
||||||
private volatile Optional<Camera> camera = Optional.absent();
|
private volatile Optional<Camera> camera = Optional.empty();
|
||||||
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
||||||
private volatile int displayOrientation = -1;
|
private volatile int displayOrientation = -1;
|
||||||
|
|
||||||
private @NonNull State state = State.PAUSED;
|
private @NonNull State state = State.PAUSED;
|
||||||
@@ -104,7 +104,7 @@ public class CameraView extends ViewGroup {
|
|||||||
Void onRunBackground() {
|
Void onRunBackground() {
|
||||||
try {
|
try {
|
||||||
long openStartMillis = System.currentTimeMillis();
|
long openStartMillis = System.currentTimeMillis();
|
||||||
camera = Optional.fromNullable(Camera.open(cameraId));
|
camera = Optional.ofNullable(Camera.open(cameraId));
|
||||||
Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
|
Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
|
||||||
synchronized (CameraView.this) {
|
synchronized (CameraView.this) {
|
||||||
CameraView.this.notifyAll();
|
CameraView.this.notifyAll();
|
||||||
@@ -145,7 +145,7 @@ public class CameraView extends ViewGroup {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPreMain() {
|
protected void onPreMain() {
|
||||||
cameraToDestroy = camera;
|
cameraToDestroy = camera;
|
||||||
camera = Optional.absent();
|
camera = Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import org.signal.core.util.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
|
|
||||||
public class EmojiEditText extends AppCompatEditText {
|
public class EmojiEditText extends AppCompatEditText {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.Emoj
|
|||||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
|
||||||
import org.thoughtcrime.securesms.util.ContextUtil;
|
import org.thoughtcrime.securesms.util.ContextUtil;
|
||||||
import org.thoughtcrime.securesms.util.DrawableUtil;
|
import org.thoughtcrime.securesms.util.DrawableUtil;
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;
|
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -19,7 +20,6 @@ import org.signal.core.util.ThreadUtil;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
||||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||||
import org.thoughtcrime.securesms.emoji.EmojiFiles;
|
|
||||||
import org.thoughtcrime.securesms.emoji.EmojiPageCache;
|
import org.thoughtcrime.securesms.emoji.EmojiPageCache;
|
||||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||||
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
||||||
@@ -101,6 +101,10 @@ public class EmojiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji, boolean jumboEmoji) {
|
static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji, boolean jumboEmoji) {
|
||||||
|
if (TextUtils.isEmpty(emoji)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
|
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
|
||||||
return getEmojiDrawable(context, drawInfo, null, jumboEmoji);
|
return getEmojiDrawable(context, drawInfo, null, jumboEmoji);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
|
|||||||
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
previousTransformationMethod = getTransformationMethod();
|
previousTransformationMethod = getTransformationMethod();
|
||||||
|
|
||||||
if (useSystemEmoji || candidates == null || candidates.size() == 0) {
|
if (useSystemEmoji || candidates == null || candidates.size() == 0) {
|
||||||
super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")), BufferType.SPANNABLE);
|
super.setText(new SpannableStringBuilder(Optional.ofNullable(text).orElse("")), BufferType.SPANNABLE);
|
||||||
} else {
|
} else {
|
||||||
CharSequence emojified = EmojiProvider.emojify(candidates, text, this, isJumbomoji || forceJumboEmoji);
|
CharSequence emojified = EmojiProvider.emojify(candidates, text, this, isJumbomoji || forceJumboEmoji);
|
||||||
super.setText(new SpannableStringBuilder(emojified), BufferType.SPANNABLE);
|
super.setText(new SpannableStringBuilder(emojified), BufferType.SPANNABLE);
|
||||||
@@ -149,7 +150,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
|
|
||||||
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
|
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
|
||||||
// We ellipsize them ourselves by manually truncating the appropriate section.
|
// We ellipsize them ourselves by manually truncating the appropriate section.
|
||||||
if (getText() != null && getText().length() > 0 && getEllipsize() == TextUtils.TruncateAt.END) {
|
if (getText() != null && getText().length() > 0 && isEllipsizedAtEnd()) {
|
||||||
if (maxLength > 0) {
|
if (maxLength > 0) {
|
||||||
ellipsizeAnyTextForMaxLength();
|
ellipsizeAnyTextForMaxLength();
|
||||||
} else if (getMaxLines() > 0) {
|
} else if (getMaxLines() > 0) {
|
||||||
@@ -162,6 +163,17 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine whether to apply custom ellipsizing logic without necessarily having the
|
||||||
|
* ellipsize property set. This allows us to work around implementations of Layout which apply an
|
||||||
|
* ellipsis even when maxLines is not set.
|
||||||
|
*/
|
||||||
|
private boolean isEllipsizedAtEnd() {
|
||||||
|
return getEllipsize() == TextUtils.TruncateAt.END ||
|
||||||
|
(getMaxLines() > 0 && getMaxLines() < Integer.MAX_VALUE) ||
|
||||||
|
maxLength > 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
||||||
|
|
||||||
@@ -201,7 +213,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
|
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||||
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
|
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
|
||||||
float measuredTextWidth = hasMetricAffectingSpan(text) ? Layout.getDesiredWidth(text, getPaint()) : getPaint().measureText(text, 0, text.length());
|
float measuredTextWidth = hasMetricAffectingSpan(text) ? Layout.getDesiredWidth(text, getPaint()) : getLongestLineWidth(text);
|
||||||
int desiredWidth = (int) measuredTextWidth + getPaddingLeft() + getPaddingRight();
|
int desiredWidth = (int) measuredTextWidth + getPaddingLeft() + getPaddingRight();
|
||||||
|
|
||||||
if (widthSpecMode == MeasureSpec.AT_MOST && desiredWidth < widthSpecSize) {
|
if (widthSpecMode == MeasureSpec.AT_MOST && desiredWidth < widthSpecSize) {
|
||||||
@@ -221,6 +233,20 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
return ((Spanned) text).nextSpanTransition(-1, text.length(), CharacterStyle.class) != text.length();
|
return ((Spanned) text).nextSpanTransition(-1, text.length(), CharacterStyle.class) != text.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float getLongestLineWidth(@NonNull CharSequence text) {
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
long maxLines = getMaxLines() > 0 ? getMaxLines() : Long.MAX_VALUE;
|
||||||
|
|
||||||
|
return Arrays.stream(text.toString().split("\n"))
|
||||||
|
.limit(maxLines)
|
||||||
|
.map(s -> getPaint().measureText(s, 0, s.length()))
|
||||||
|
.max(Float::compare)
|
||||||
|
.orElse(0f);
|
||||||
|
}
|
||||||
|
|
||||||
public int getLastLineWidth() {
|
public int getLastLineWidth() {
|
||||||
return lastLineWidth;
|
return lastLineWidth;
|
||||||
}
|
}
|
||||||
@@ -291,7 +317,7 @@ public class EmojiTextView extends AppCompatTextView {
|
|||||||
SpannableStringBuilder newContent = new SpannableStringBuilder();
|
SpannableStringBuilder newContent = new SpannableStringBuilder();
|
||||||
newContent.append(getText().subSequence(0, overflowStart))
|
newContent.append(getText().subSequence(0, overflowStart))
|
||||||
.append(ellipsized.subSequence(0, ellipsized.length()))
|
.append(ellipsized.subSequence(0, ellipsized.length()))
|
||||||
.append(Optional.fromNullable(overflowText).or(""));
|
.append(Optional.ofNullable(overflowText).orElse(""));
|
||||||
|
|
||||||
EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent);
|
EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent);
|
||||||
CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji);
|
CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package org.thoughtcrime.securesms.components.emoji;
|
package org.thoughtcrime.securesms.components.emoji;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatImageButton;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
@@ -24,17 +26,17 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
|
|||||||
|
|
||||||
public EmojiToggle(Context context) {
|
public EmojiToggle(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
initialize();
|
initialize(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmojiToggle(Context context, AttributeSet attrs) {
|
public EmojiToggle(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
initialize();
|
initialize(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmojiToggle(Context context, AttributeSet attrs, int defStyle) {
|
public EmojiToggle(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
initialize();
|
initialize(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToMedia() {
|
public void setToMedia() {
|
||||||
@@ -45,11 +47,18 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
|
|||||||
setImageDrawable(imeToggle);
|
setImageDrawable(imeToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize() {
|
private void initialize(@Nullable AttributeSet attrs) {
|
||||||
this.emojiToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_emoji);
|
boolean forceOutline = false;
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiToggle, 0, 0);
|
||||||
|
forceOutline = typedArray.getBoolean(R.styleable.EmojiToggle_force_outline, false);
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emojiToggle = ContextUtil.requireDrawable(getContext(), forceOutline ? R.drawable.ic_emoji_outline : R.drawable.ic_emoji);
|
||||||
this.stickerToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_sticker_24);
|
this.stickerToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_sticker_24);
|
||||||
this.gifToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_gif_24);
|
this.gifToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_gif_24);
|
||||||
this.imeToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_keyboard_24);
|
this.imeToggle = ContextUtil.requireDrawable(getContext(), forceOutline ? R.drawable.ic_keyboard_outline_24 : R.drawable.ic_keyboard_24);
|
||||||
this.mediaToggle = emojiToggle;
|
this.mediaToggle = emojiToggle;
|
||||||
|
|
||||||
setToMedia();
|
setToMedia();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
|
|||||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||||
import org.thoughtcrime.securesms.emoji.ObsoleteEmoji;
|
import org.thoughtcrime.securesms.emoji.ObsoleteEmoji;
|
||||||
import org.thoughtcrime.securesms.util.StringUtil;
|
import org.signal.core.util.StringUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -18,6 +18,7 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
public final class EmojiUtil {
|
public final class EmojiUtil {
|
||||||
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
|
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
|
||||||
|
private static final String EMOJI_REGEX = "[^\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}\\p{Cf}\\p{Cs}\\s]";
|
||||||
|
|
||||||
private EmojiUtil() {}
|
private EmojiUtil() {}
|
||||||
|
|
||||||
@@ -84,4 +85,12 @@ public final class EmojiUtil {
|
|||||||
|
|
||||||
return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches();
|
return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String stripEmoji(@Nullable String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.replaceAll(EMOJI_REGEX, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import android.widget.FrameLayout;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.res.TypedArrayUtils;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
@@ -23,7 +21,6 @@ import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
|||||||
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
|
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
|
||||||
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment;
|
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment;
|
||||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment;
|
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.ThemedFragment;
|
import org.thoughtcrime.securesms.util.ThemedFragment;
|
||||||
|
|
||||||
public class MediaKeyboard extends FrameLayout implements InputView {
|
public class MediaKeyboard extends FrameLayout implements InputView {
|
||||||
@@ -149,6 +146,10 @@ public class MediaKeyboard extends FrameLayout implements InputView {
|
|||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmojiSearchMode() {
|
||||||
|
return keyboardState == State.EMOJI_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
private void initView() {
|
private void initView() {
|
||||||
if (!isInitialised) {
|
if (!isInitialised) {
|
||||||
|
|
||||||
|
|||||||