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 |
23
.github/stale.yml
vendored
@@ -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("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:
|
||||
connection.rollback()
|
||||
print("Error: %s" % e.args[0])
|
||||
|
||||
@@ -63,8 +63,8 @@ ktlint {
|
||||
version = "0.43.2"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 1019
|
||||
def canonicalVersionName = "5.33.0"
|
||||
def canonicalVersionCode = 1044
|
||||
def canonicalVersionName = "5.36.3"
|
||||
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
@@ -455,8 +455,9 @@ dependencies {
|
||||
implementation project(':device-transfer')
|
||||
implementation project(':image-editor')
|
||||
implementation project(':donations')
|
||||
implementation project(':contacts')
|
||||
|
||||
implementation libs.signal.client.android
|
||||
implementation libs.libsignal.android
|
||||
implementation libs.google.protobuf.javalite
|
||||
|
||||
implementation(libs.mobilecoin) {
|
||||
@@ -524,10 +525,7 @@ dependencies {
|
||||
testImplementation testLibs.junit.junit
|
||||
testImplementation testLibs.assertj.core
|
||||
testImplementation testLibs.mockito.core
|
||||
testImplementation testLibs.powermock.api.mockito
|
||||
testImplementation testLibs.powermock.module.junit4.core
|
||||
testImplementation testLibs.powermock.module.junit4.rule
|
||||
testImplementation testLibs.powermock.classloading.xstream
|
||||
testImplementation testLibs.mockito.kotlin
|
||||
|
||||
testImplementation testLibs.androidx.test.core
|
||||
testImplementation (testLibs.robolectric.robolectric) {
|
||||
@@ -554,7 +552,7 @@ dependencies {
|
||||
implementation libs.rxjava3.rxkotlin
|
||||
implementation libs.rxdogtag
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||
}
|
||||
|
||||
def getLastCommitTimestamp() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
-dontobfuscate
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class org.whispersystems.** { *; }
|
||||
-keep class org.signal.libsignal.protocol.** { *; }
|
||||
-keep class org.thoughtcrime.securesms.** { *; }
|
||||
-keepclassmembers class ** {
|
||||
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.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.UUID
|
||||
|
||||
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.recipients.Recipient
|
||||
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.PNI
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -523,7 +522,7 @@ class RecipientDatabaseTest {
|
||||
}
|
||||
|
||||
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())
|
||||
assertEquals(0, cursor.getLong(0))
|
||||
}
|
||||
|
||||
@@ -9,13 +9,16 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
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.DecryptedMember
|
||||
import org.signal.zkgroup.groups.GroupMasterKey
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
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.RecipientId
|
||||
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.PNI
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -76,8 +75,6 @@ class RecipientDatabaseTest_merges {
|
||||
|
||||
SignalStore.account().setAci(localAci)
|
||||
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. */
|
||||
@@ -217,19 +214,12 @@ class RecipientDatabaseTest_merges {
|
||||
private val context: Application
|
||||
get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
|
||||
|
||||
private fun ensureDbEmpty() {
|
||||
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 {
|
||||
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
|
||||
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 {
|
||||
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.absent())
|
||||
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.empty())
|
||||
}
|
||||
|
||||
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.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.KbsData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
|
||||
@@ -179,11 +179,12 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity android:name=".sharing.ShareActivity"
|
||||
<activity android:name=".sharing.v2.ShareActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity=""
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
@@ -403,7 +404,7 @@
|
||||
<activity
|
||||
android:name=".stories.viewer.StoryViewerActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
|
||||
|
||||
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
||||
@@ -757,6 +758,8 @@
|
||||
|
||||
<receiver android:name=".service.ExpirationListener" />
|
||||
|
||||
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
|
||||
|
||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||
|
||||
<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 |
BIN
app/src/main/assets/emoji/People_9.webp
Normal file
|
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.tracing.Tracer;
|
||||
import org.signal.glide.SignalGlideCodecs;
|
||||
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
||||
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.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
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.VersionTracker;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -201,6 +202,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
|
||||
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
|
||||
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
|
||||
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
|
||||
.execute();
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
@@ -58,6 +59,12 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(@NonNull Context newBase) {
|
||||
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
super.attachBaseContext(newBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
super.onCreate(savedInstanceState, ready);
|
||||
@@ -132,7 +139,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable {
|
||||
@@ -60,6 +60,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
// Intentionally Blank.
|
||||
}
|
||||
|
||||
default void updateSelectedState() {
|
||||
// Intentionall Blank.
|
||||
}
|
||||
|
||||
interface EventListener {
|
||||
void onQuoteClicked(MmsMessageRecord messageRecord);
|
||||
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||
@@ -94,6 +98,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onChangeNumberUpdateContact(@NonNull Recipient recipient);
|
||||
void onCallToAction(@NonNull String action);
|
||||
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 */
|
||||
boolean onUrlClicked(@NonNull String url);
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
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.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.
|
||||
|
||||
@@ -27,16 +27,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
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.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -153,7 +153,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
@Override
|
||||
protected Void doInBackground(Context... params) {
|
||||
try {
|
||||
DirectoryHelper.refreshDirectory(params[0], true);
|
||||
ContactDiscovery.refreshAll(params[0], true);
|
||||
} catch (IOException 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.LetterHeaderDecoration;
|
||||
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.ui.GroupLimitDialog;
|
||||
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.adapter.FixedViewsAdapter;
|
||||
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.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -576,7 +576,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
DirectoryHelper.refreshDirectory(context, false);
|
||||
ContactDiscovery.refreshAll(context, false);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
@@ -637,8 +637,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(ContactSelectionListItem contact) {
|
||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orElse(null), contact.getNumber())
|
||||
: SelectedContact.forPhone(contact.getRecipientId().orElse(null), contact.getNumber());
|
||||
|
||||
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();
|
||||
@@ -774,7 +774,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
markContactUnselected(selectedContact);
|
||||
|
||||
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.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
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.dependencies.ApplicationDependencies;
|
||||
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.TextSecurePreferences;
|
||||
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.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
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.task.ProgressDialogAsyncTask;
|
||||
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.function.Consumer;
|
||||
|
||||
@@ -251,7 +251,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
for (SelectedContact contact : contacts) {
|
||||
RecipientId recipientId = contact.getOrCreateRecipientId(context);
|
||||
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);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
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.stories.Stories;
|
||||
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.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
|
||||
public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner {
|
||||
@@ -61,8 +58,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||
ConversationListTabRepository repository = new ConversationListTabRepository();
|
||||
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
|
||||
|
||||
navigator.onCreate(savedInstanceState);
|
||||
|
||||
handleGroupLinkInIntent(getIntent());
|
||||
handleProxyInIntent(getIntent());
|
||||
handleSignalMeIntent(getIntent());
|
||||
@@ -70,18 +65,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||
CachedInflater.from(this).clear();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -135,11 +118,11 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||
private void updateTabVisibility() {
|
||||
if (Stories.isFeatureEnabled()) {
|
||||
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 {
|
||||
findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE);
|
||||
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.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||
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.insights.InsightsLauncher;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment;
|
||||
|
||||
public class MainNavigator {
|
||||
|
||||
public static final String STORIES_TAG = "STORIES";
|
||||
|
||||
public static final int REQUEST_CONFIG_CHANGES = 901;
|
||||
|
||||
private final MainActivity activity;
|
||||
@@ -38,16 +31,6 @@ public class MainNavigator {
|
||||
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
|
||||
* to the system to do the default behavior.
|
||||
@@ -76,29 +59,6 @@ public class MainNavigator {
|
||||
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() {
|
||||
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.attachments.DatabaseAttachment;
|
||||
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.MediaRecord;
|
||||
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.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sharing.ShareActivity;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
@@ -87,7 +90,8 @@ import java.util.Objects;
|
||||
public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
|
||||
MediaRailAdapter.RailItemListener,
|
||||
MediaPreviewFragment.Events
|
||||
MediaPreviewFragment.Events,
|
||||
VoiceNoteMediaControllerOwner
|
||||
{
|
||||
|
||||
private final static String TAG = Log.tag(MediaPreviewActivity.class);
|
||||
@@ -127,6 +131,8 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
private MediaDatabase.Sorting sorting;
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
|
||||
private VoiceNoteMediaController voiceNoteMediaController;
|
||||
|
||||
private @Nullable Cursor cursor = null;
|
||||
|
||||
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
|
||||
@@ -159,6 +165,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
|
||||
voiceNoteMediaController = new VoiceNoteMediaController(this);
|
||||
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
||||
|
||||
fullscreenHelper = new FullscreenHelper(this);
|
||||
@@ -203,23 +210,25 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
else from = "";
|
||||
|
||||
if (showThread) {
|
||||
String to = null;
|
||||
String titleText = null;
|
||||
Recipient threadRecipient = mediaItem.threadRecipient;
|
||||
|
||||
if (threadRecipient != null) {
|
||||
if (mediaItem.outgoing || threadRecipient.isGroup()) {
|
||||
if (mediaItem.outgoing) {
|
||||
if (threadRecipient.isSelf()) {
|
||||
from = getString(R.string.note_to_self);
|
||||
titleText = getString(R.string.note_to_self);
|
||||
} else {
|
||||
to = threadRecipient.getDisplayName(this);
|
||||
titleText = getString(R.string.MediaPreviewActivity_you_to_s, threadRecipient.getDisplayName(this));
|
||||
}
|
||||
} 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 to != null ? getString(R.string.MediaPreviewActivity_s_to_s, from, to)
|
||||
: from;
|
||||
return titleText != null ? titleText : from;
|
||||
} else {
|
||||
return from;
|
||||
}
|
||||
@@ -286,7 +295,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -386,10 +395,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
MediaItem mediaItem = getCurrentMediaItem();
|
||||
|
||||
if (mediaItem != null) {
|
||||
Intent composeIntent = new Intent(this, ShareActivity.class);
|
||||
composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri);
|
||||
composeIntent.setType(mediaItem.type);
|
||||
startActivity(composeIntent);
|
||||
MultiselectForwardFragmentArgs.create(
|
||||
this,
|
||||
mediaItem.uri,
|
||||
mediaItem.type,
|
||||
args -> MultiselectForwardFragment.showBottomSheet(getSupportFragmentManager(), args)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,6 +608,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaReady() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() {
|
||||
return voiceNoteMediaController;
|
||||
}
|
||||
|
||||
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
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.database.SignalDatabase;
|
||||
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.recipients.Recipient;
|
||||
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.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -79,7 +79,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
|
||||
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
|
||||
try {
|
||||
DirectoryHelper.refreshDirectoryFor(this, resolved, false);
|
||||
ContactDiscovery.refresh(this, resolved, false);
|
||||
resolved = Recipient.resolved(resolved.getId());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
|
||||
|
||||
@@ -61,7 +61,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
||||
passphrase);
|
||||
|
||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||
SignalStore.account().generateAciIdentityKey();
|
||||
SignalStore.account().generateAciIdentityKeyIfNecessary();
|
||||
SignalStore.account().generatePniIdentityKeyIfNecessary();
|
||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class TransportOption implements Parcelable {
|
||||
|
||||
@@ -18,8 +20,8 @@ public class TransportOption implements Parcelable {
|
||||
TEXTSECURE
|
||||
}
|
||||
|
||||
private final int drawable;
|
||||
private final int backgroundColor;
|
||||
private final int drawable;
|
||||
private final int backgroundColor;
|
||||
private final @NonNull String text;
|
||||
private final @NonNull Type type;
|
||||
private final @NonNull String composeHint;
|
||||
@@ -35,7 +37,7 @@ public class TransportOption implements Parcelable {
|
||||
@NonNull CharacterCalculator characterCalculator)
|
||||
{
|
||||
this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
|
||||
Optional.<CharSequence>absent(), Optional.<Integer>absent());
|
||||
Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public TransportOption(@NonNull Type type,
|
||||
@@ -64,8 +66,8 @@ public class TransportOption implements Parcelable {
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
CharacterCalculator.readFromParcel(in),
|
||||
Optional.fromNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
|
||||
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.absent());
|
||||
Optional.ofNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
|
||||
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.empty());
|
||||
}
|
||||
|
||||
public @NonNull Type getType() {
|
||||
@@ -123,7 +125,7 @@ public class TransportOption implements Parcelable {
|
||||
dest.writeString(text);
|
||||
dest.writeString(composeHint);
|
||||
CharacterCalculator.writeToParcel(dest, characterCalculator);
|
||||
TextUtils.writeToParcel(simName.orNull(), dest, flags);
|
||||
TextUtils.writeToParcel(simName.orElse(null), dest, flags);
|
||||
|
||||
if (simSubscriptionId.isPresent()) {
|
||||
dest.writeInt(1);
|
||||
|
||||
@@ -14,13 +14,14 @@ import org.thoughtcrime.securesms.util.PushCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||
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.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
|
||||
@@ -33,8 +34,8 @@ public class TransportOptions {
|
||||
private final List<TransportOption> enabledTransports;
|
||||
|
||||
private Type defaultTransportType = Type.SMS;
|
||||
private Optional<Integer> defaultSubscriptionId = Optional.absent();
|
||||
private Optional<TransportOption> selectedOption = Optional.absent();
|
||||
private Optional<Integer> defaultSubscriptionId = Optional.empty();
|
||||
private Optional<TransportOption> selectedOption = Optional.empty();
|
||||
|
||||
private final Optional<Integer> systemSubscriptionId;
|
||||
|
||||
@@ -54,7 +55,7 @@ public class TransportOptions {
|
||||
setSelectedTransport(null);
|
||||
} else {
|
||||
this.defaultTransportType = Type.SMS;
|
||||
this.defaultSubscriptionId = Optional.absent();
|
||||
this.defaultSubscriptionId = Optional.empty();
|
||||
|
||||
notifyTransportChangeListeners();
|
||||
}
|
||||
@@ -81,7 +82,7 @@ public class TransportOptions {
|
||||
}
|
||||
|
||||
public void setSelectedTransport(@Nullable TransportOption transportOption) {
|
||||
this.selectedOption = Optional.fromNullable(transportOption);
|
||||
this.selectedOption = Optional.ofNullable(transportOption);
|
||||
notifyTransportChangeListeners();
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ public class TransportOptions {
|
||||
if (selectedOption.isPresent()) return selectedOption.get();
|
||||
|
||||
if (defaultTransportType == Type.SMS) {
|
||||
TransportOption transportOption = findEnabledSmsTransportOption(defaultSubscriptionId.or(systemSubscriptionId));
|
||||
TransportOption transportOption = findEnabledSmsTransportOption(OptionalUtil.or(defaultSubscriptionId, systemSubscriptionId));
|
||||
if (transportOption != null) {
|
||||
return transportOption;
|
||||
}
|
||||
@@ -124,7 +125,7 @@ public class TransportOptions {
|
||||
|
||||
for (TransportOption transportOption : enabledTransports) {
|
||||
if (transportOption.getType() == Type.SMS &&
|
||||
subId == transportOption.getSimSubscriptionId().or(-1)) {
|
||||
subId == transportOption.getSimSubscriptionId().orElse(-1)) {
|
||||
return transportOption;
|
||||
}
|
||||
}
|
||||
@@ -133,7 +134,7 @@ public class TransportOptions {
|
||||
}
|
||||
|
||||
public void disableTransport(Type type) {
|
||||
TransportOption selected = selectedOption.orNull();
|
||||
TransportOption selected = selectedOption.orElse(null);
|
||||
|
||||
Iterator<TransportOption> iterator = enabledTransports.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
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.ThrottledDebouncer;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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;
|
||||
|
||||
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 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 DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
|
||||
@@ -111,6 +116,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
||||
private ThrottledDebouncer requestNewSizesThrottle;
|
||||
|
||||
private Disposable ephemeralStateDisposable = Disposable.empty();
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(@NonNull Context newBase) {
|
||||
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
@@ -153,6 +160,18 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
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
|
||||
public void onResume() {
|
||||
Log.i(TAG, "onResume()");
|
||||
@@ -193,6 +212,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
Log.i(TAG, "onStop");
|
||||
super.onStop();
|
||||
|
||||
ephemeralStateDisposable.dispose();
|
||||
|
||||
if (!isInPipMode() || isFinishing()) {
|
||||
EventBus.getDefault().unregister(this);
|
||||
requestNewSizesThrottle.clear();
|
||||
@@ -295,7 +316,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
|
||||
LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(),
|
||||
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));
|
||||
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
|
||||
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() {
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable));
|
||||
@@ -623,6 +650,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
handleCallPreJoin(event); break;
|
||||
case CALL_CONNECTED:
|
||||
handleCallConnected(event); break;
|
||||
case CALL_RECONNECTING:
|
||||
handleCallReconnecting(); break;
|
||||
case NETWORK_FAILURE:
|
||||
handleServerFailure(); break;
|
||||
case CALL_RINGING:
|
||||
@@ -807,7 +836,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
if (feature.isPresent()) {
|
||||
FoldingFeature foldingFeature = (FoldingFeature) feature.get();
|
||||
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");
|
||||
viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
|
||||
} 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.stickers.StickerLocator;
|
||||
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.SignalServiceDataMessage;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
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) {
|
||||
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.absent();
|
||||
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.empty();
|
||||
|
||||
String encodedKey = null;
|
||||
|
||||
@@ -103,12 +103,12 @@ public class PointerAttachment extends Attachment {
|
||||
|
||||
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
pointer.get().asPointer().getSize().or(0),
|
||||
pointer.get().asPointer().getFileName().orNull(),
|
||||
pointer.get().asPointer().getSize().orElse(0),
|
||||
pointer.get().asPointer().getFileName().orElse(null),
|
||||
pointer.get().asPointer().getCdnNumber(),
|
||||
pointer.get().asPointer().getRemoteId().toString(),
|
||||
encodedKey, null,
|
||||
pointer.get().asPointer().getDigest().orNull(),
|
||||
pointer.get().asPointer().getDigest().orElse(null),
|
||||
fastPreflightId,
|
||||
pointer.get().asPointer().getVoiceNote(),
|
||||
pointer.get().asPointer().isBorderless(),
|
||||
@@ -116,9 +116,9 @@ public class PointerAttachment extends Attachment {
|
||||
pointer.get().asPointer().getWidth(),
|
||||
pointer.get().asPointer().getHeight(),
|
||||
pointer.get().asPointer().getUploadTimestamp(),
|
||||
pointer.get().asPointer().getCaption().orNull(),
|
||||
pointer.get().asPointer().getCaption().orElse(null),
|
||||
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(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0,
|
||||
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
|
||||
pointer.getFileName(),
|
||||
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
||||
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
||||
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
|
||||
null,
|
||||
thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null,
|
||||
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
@@ -141,7 +141,7 @@ public class PointerAttachment extends Attachment {
|
||||
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
||||
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
||||
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
||||
thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null,
|
||||
thumbnail != null ? thumbnail.asPointer().getCaption().orElse(null) : null,
|
||||
null,
|
||||
null));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
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.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.whispersystems.libsignal.util.guava.Optional
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
import javax.annotation.meta.Exhaustive
|
||||
|
||||
/**
|
||||
@@ -128,6 +128,6 @@ object AvatarRenderer {
|
||||
}
|
||||
|
||||
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) {
|
||||
val textSize = Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f)
|
||||
val width = bounds.width()
|
||||
val textSize = Avatars.getTextSizeForLength(context, avatar.text, width * 0.8f, width * 0.45f)
|
||||
val candidates = EmojiProvider.getCandidates(avatar.text)
|
||||
|
||||
textPaint.textSize = textSize
|
||||
|
||||
@@ -26,7 +26,10 @@ class AvatarView @JvmOverloads constructor(
|
||||
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 fun showStoryRing(hasUnreadStory: Boolean) {
|
||||
@@ -37,8 +40,8 @@ class AvatarView @JvmOverloads constructor(
|
||||
storyRing.visible = true
|
||||
storyRing.isActivated = hasUnreadStory
|
||||
|
||||
avatar.scaleX = 0.82f
|
||||
avatar.scaleY = 0.82f
|
||||
avatar.scaleX = 0.8f
|
||||
avatar.scaleY = 0.8f
|
||||
}
|
||||
|
||||
private fun hideStoryRing() {
|
||||
@@ -48,6 +51,10 @@ class AvatarView @JvmOverloads constructor(
|
||||
avatar.scaleY = 1f
|
||||
}
|
||||
|
||||
fun hasStory(): Boolean {
|
||||
return storyRing.visible
|
||||
}
|
||||
|
||||
fun setStoryRingFromState(storyViewState: StoryViewState) {
|
||||
when (storyViewState) {
|
||||
StoryViewState.NONE -> hideStoryRing()
|
||||
|
||||
@@ -23,6 +23,8 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -110,7 +112,7 @@ public class BackupDialog {
|
||||
|
||||
@RequiresApi(29)
|
||||
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)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
@@ -141,7 +143,7 @@ public class BackupDialog {
|
||||
}
|
||||
|
||||
public static void showDisableBackupDialog(@NonNull Context context, @NonNull Runnable onBackupsDisabled) {
|
||||
new AlertDialog.Builder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.BackupDialog_delete_backups)
|
||||
.setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
||||
@@ -5,8 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@@ -18,10 +18,11 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.Conversions;
|
||||
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.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
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.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.signal.core.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
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.FileOutputStream;
|
||||
|
||||
@@ -17,6 +17,8 @@ import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.Conversions;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
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.BackupFrame;
|
||||
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.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -69,6 +69,18 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
@SuppressWarnings("unused")
|
||||
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,
|
||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||
throws IOException
|
||||
@@ -85,12 +97,12 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
int count = 0;
|
||||
|
||||
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
keyValueDatabase.beginTransaction();
|
||||
try {
|
||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
||||
|
||||
db.beginTransaction();
|
||||
keyValueDatabase.beginTransaction();
|
||||
|
||||
dropAllTables(db);
|
||||
|
||||
BackupFrame frame;
|
||||
@@ -272,12 +284,17 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
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)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
String type = cursor.getString(1);
|
||||
|
||||
if ("table".equals(type) && !name.startsWith("sqlite_")) {
|
||||
Log.i(TAG, "Dropping table: " + 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.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
class BadgeImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
||||
@@ -9,13 +9,13 @@ import com.google.android.flexbox.FlexboxLayoutManager
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.util.Pair
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.badges.models.Badge.Category.Companion.fromCode
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity
|
||||
import org.whispersystems.libsignal.util.Pair
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import java.math.BigDecimal
|
||||
import java.sql.Timestamp
|
||||
|
||||
@@ -45,6 +45,7 @@ class CantProcessSubscriptionPaymentBottomSheetDialogFragment : DSLSettingsBotto
|
||||
text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again)
|
||||
) {
|
||||
SignalStore.donationsValues().showCantProcessDialog = false
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
sectionHeaderPref(
|
||||
DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__your_badge_has_expired
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__subscription_cancelled
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__monthly_donation_cancelled
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
@@ -54,11 +54,11 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
noPadTextPref(
|
||||
DSLSettingsText.from(
|
||||
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) {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_automatically, badge.name)
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
|
||||
} else {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_canceled)
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled)
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
@@ -72,7 +72,7 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
if (isLikelyASustainer) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__to_continue_supporting_technology
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_keep
|
||||
}
|
||||
} else {
|
||||
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.util.InternetConnectionObserver
|
||||
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)
|
||||
|
||||
@@ -54,13 +54,13 @@ class BadgesOverviewViewModel(
|
||||
subscriptionsRepository.getSubscriptions()
|
||||
) { active, all ->
|
||||
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 {
|
||||
Optional.absent()
|
||||
Optional.empty()
|
||||
}
|
||||
}.subscribeBy(
|
||||
onSuccess = { badgeId ->
|
||||
store.update { it.copy(fadedBadgeId = badgeId.orNull()) }
|
||||
store.update { it.copy(fadedBadgeId = badgeId.orElse(null)) }
|
||||
},
|
||||
onError = { 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.ViewModelProvider
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.badges.BadgeRepository
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
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.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener {
|
||||
@@ -88,7 +88,7 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
|
||||
@Override
|
||||
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)
|
||||
.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.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -64,7 +65,9 @@ final class BlockedUsersAdapter extends ListAdapter<Recipient, BlockedUsersAdapt
|
||||
displayName.setText(recipient.getDisplayName(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) {
|
||||
numberOrUsername.setText(identifier);
|
||||
|
||||
@@ -95,7 +95,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
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);
|
||||
|
||||
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.keyvalue.SignalStore;
|
||||
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 java.util.List;
|
||||
|
||||
@@ -41,10 +41,10 @@ import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ConversationItemFooter extends ConstraintLayout {
|
||||
|
||||
@@ -11,15 +11,11 @@ import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ConversationTypingView extends ConstraintLayout {
|
||||
|
||||
@@ -7,11 +7,9 @@ import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class CornerMask {
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
|
||||
public class DocumentView extends FrameLayout {
|
||||
|
||||
@@ -105,11 +106,11 @@ public class DocumentView extends FrameLayout {
|
||||
|
||||
this.documentSlide = documentSlide;
|
||||
|
||||
this.fileName.setText(documentSlide.getFileName()
|
||||
.or(documentSlide.getCaption())
|
||||
.or(getContext().getString(R.string.DocumentView_unnamed_file)));
|
||||
this.fileName.setText(OptionalUtil.or(documentSlide.getFileName(),
|
||||
documentSlide.getCaption())
|
||||
.orElse(getContext().getString(R.string.DocumentView_unnamed_file)));
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
@@ -26,6 +28,9 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
|
||||
@StyleRes
|
||||
protected open val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners
|
||||
|
||||
@ColorInt
|
||||
protected var backgroundColor: Int = Color.TRANSPARENT
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NORMAL, themeResId)
|
||||
@@ -44,7 +49,8 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
|
||||
val dialogBackground = MaterialShapeDrawable(shapeAppearanceModel)
|
||||
|
||||
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() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
|
||||
@@ -62,7 +62,7 @@ public class FromTextView extends SimpleEmojiTextView {
|
||||
builder.append(suffix);
|
||||
}
|
||||
|
||||
if (recipient.isReleaseNotes()) {
|
||||
if (recipient.showVerified()) {
|
||||
Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_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 org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
|
||||
/**
|
||||
* 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.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class InputPanel extends LinearLayout
|
||||
@@ -181,13 +181,19 @@ public class InputPanel extends LinearLayout
|
||||
@NonNull CharSequence body,
|
||||
@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()
|
||||
: 0;
|
||||
|
||||
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) {
|
||||
quoteAnimator.cancel();
|
||||
@@ -252,7 +258,7 @@ public class InputPanel extends LinearLayout
|
||||
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()));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Guideline;
|
||||
|
||||
import org.signal.glide.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ abstract class KeyboardEntryDialogFragment(@LayoutRes contentLayoutId: Int) :
|
||||
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
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
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.
|
||||
|
||||
@@ -19,7 +19,6 @@ import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
@@ -101,7 +100,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
|
||||
if (listener != null) listener.onRecordPermissionRequired();
|
||||
} else {
|
||||
} else if (state == State.NOT_RUNNING) {
|
||||
state = State.RUNNING_HELD;
|
||||
floatingRecordButton.display(event.getX(), event.getY());
|
||||
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.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||
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.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Projection;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
@@ -49,7 +53,9 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
PREVIEW(0),
|
||||
OUTGOING(1),
|
||||
INCOMING(2),
|
||||
STORY_REPLY(3);
|
||||
STORY_REPLY_OUTGOING(3),
|
||||
STORY_REPLY_INCOMING(4),
|
||||
STORY_REPLY_PREVIEW(5);
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -68,16 +74,18 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
}
|
||||
}
|
||||
|
||||
private ViewGroup mainView;
|
||||
private ViewGroup footerView;
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private View quoteBarView;
|
||||
private ImageView thumbnailView;
|
||||
private View attachmentVideoOverlayView;
|
||||
private ViewGroup attachmentContainerView;
|
||||
private TextView attachmentNameView;
|
||||
private ImageView dismissView;
|
||||
private ViewGroup mainView;
|
||||
private ViewGroup footerView;
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private View quoteBarView;
|
||||
private ImageView thumbnailView;
|
||||
private View attachmentVideoOverlayView;
|
||||
private ViewGroup attachmentContainerView;
|
||||
private TextView attachmentNameView;
|
||||
private ImageView dismissView;
|
||||
private EmojiImageView missingStoryReaction;
|
||||
private EmojiImageView storyReactionEmoji;
|
||||
|
||||
private long id;
|
||||
private LiveRecipient author;
|
||||
@@ -129,6 +137,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
this.dismissView = findViewById(R.id.quote_dismiss);
|
||||
this.mediaDescriptionText = findViewById(R.id.media_type);
|
||||
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.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);
|
||||
cornerMask.setTopLeftRadius(radius);
|
||||
cornerMask.setTopRightRadius(radius);
|
||||
} else if (messageType == MessageType.STORY_REPLY) {
|
||||
} else if (isStoryReply()) {
|
||||
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
|
||||
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
|
||||
}
|
||||
|
||||
mainView.setMinimumHeight(thumbHeight);
|
||||
|
||||
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
|
||||
params.height = thumbHeight;
|
||||
params.width = thumbWidth;
|
||||
@@ -197,7 +205,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
@Nullable CharSequence body,
|
||||
boolean originalMissing,
|
||||
@NonNull SlideDeck attachments,
|
||||
@Nullable ChatColors chatColors)
|
||||
@Nullable ChatColors chatColors,
|
||||
@Nullable String storyReaction)
|
||||
{
|
||||
if (this.author != null) this.author.removeForeverObserver(this);
|
||||
|
||||
@@ -208,8 +217,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
|
||||
this.author.observeForever(this);
|
||||
setQuoteAuthor(author);
|
||||
setQuoteText(body, attachments);
|
||||
setQuoteAttachment(glideRequests, attachments);
|
||||
setQuoteText(body, attachments, originalMissing, storyReaction);
|
||||
setQuoteAttachment(glideRequests, body, attachments, originalMissing);
|
||||
setQuoteMissingFooter(originalMissing);
|
||||
|
||||
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) {
|
||||
boolean outgoing = messageType != MessageType.INCOMING;
|
||||
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY;
|
||||
boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING;
|
||||
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)
|
||||
: getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
|
||||
} else {
|
||||
@@ -259,14 +268,70 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
: author.getDisplayName(getContext()));
|
||||
}
|
||||
|
||||
quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing ? R.color.core_white : android.R.color.transparent));
|
||||
mainView.setBackgroundColor(ContextCompat.getColor(getContext(), preview ? R.color.quote_preview_background : R.color.quote_view_background));
|
||||
quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing || isStoryReply() ? R.color.core_white : android.R.color.transparent));
|
||||
|
||||
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 (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.setText(body == null ? "" : body);
|
||||
mediaDescriptionText.setVisibility(GONE);
|
||||
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 documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).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){
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(VISIBLE);
|
||||
attachmentNameView.setText(documentSlide.getFileName().or(""));
|
||||
attachmentNameView.setText(documentSlide.getFileName().orElse(""));
|
||||
} else {
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(GONE);
|
||||
@@ -343,10 +423,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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) {
|
||||
bodyView.setTextSize(unit, size);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ public class SearchToolbar extends LinearLayout {
|
||||
EditText searchText = searchView.findViewById(R.id.search_src_text);
|
||||
|
||||
searchView.setSubmitButtonEnabled(false);
|
||||
searchView.setMaxWidth(Integer.MAX_VALUE);
|
||||
|
||||
if (searchText != null) searchText.setHint(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() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
if (listener != null) listener.onSearchTextChange(query);
|
||||
if (listener != null) {
|
||||
listener.onSearchTextChange(query);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ import org.thoughtcrime.securesms.TransportOptions;
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
import org.thoughtcrime.securesms.TransportOptionsPopup;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class SendButton extends AppCompatImageButton
|
||||
implements TransportOptions.OnTransportChangedListener,
|
||||
@@ -22,7 +24,7 @@ public class SendButton extends AppCompatImageButton
|
||||
|
||||
private final TransportOptions transportOptions;
|
||||
|
||||
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.absent();
|
||||
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.empty();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
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[] measureDimens = new int[2];
|
||||
|
||||
private Optional<TransferControlView> transferControls = Optional.absent();
|
||||
private Optional<TransferControlView> transferControls = Optional.empty();
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener downloadClickListener = null;
|
||||
private Slide slide = null;
|
||||
@@ -391,6 +392,32 @@ public class ThumbnailView extends FrameLayout {
|
||||
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) {
|
||||
this.thumbnailClickListener = listener;
|
||||
}
|
||||
@@ -401,11 +428,15 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
public void clear(GlideRequests glideRequests) {
|
||||
glideRequests.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
|
||||
if (transferControls.isPresent()) {
|
||||
getTransferControls().clear();
|
||||
}
|
||||
|
||||
glideRequests.clear(blurhash);
|
||||
blurhash.setImageDrawable(null);
|
||||
|
||||
slide = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,20 @@ package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
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.davemorrissey.labs.subscaleview.ImageSource;
|
||||
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.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.ActionRequestListener;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
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.InputStream;
|
||||
@@ -79,7 +85,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
}
|
||||
|
||||
@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 int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
||||
@@ -101,15 +107,16 @@ public class ZoomingImageView extends FrameLayout {
|
||||
|
||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||
Log.i(TAG, "Loading in standard image view...");
|
||||
setImageViewUri(glideRequests, uri);
|
||||
setImageViewUri(glideRequests, uri, onMediaReady);
|
||||
} else {
|
||||
Log.i(TAG, "Loading in subsampling image view...");
|
||||
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);
|
||||
subsamplingImageView.setVisibility(View.GONE);
|
||||
|
||||
@@ -117,6 +124,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontTransform()
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.addListener(ActionRequestListener.onEither(onMediaReady))
|
||||
.into(photoView);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,12 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraView extends ViewGroup {
|
||||
@@ -56,8 +56,8 @@ public class CameraView extends ViewGroup {
|
||||
private final CameraSurfaceView surface;
|
||||
private final OnOrientationChange onOrientationChange;
|
||||
|
||||
private volatile Optional<Camera> camera = Optional.absent();
|
||||
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
||||
private volatile Optional<Camera> camera = Optional.empty();
|
||||
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
||||
private volatile int displayOrientation = -1;
|
||||
|
||||
private @NonNull State state = State.PAUSED;
|
||||
@@ -104,7 +104,7 @@ public class CameraView extends ViewGroup {
|
||||
Void onRunBackground() {
|
||||
try {
|
||||
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");
|
||||
synchronized (CameraView.this) {
|
||||
CameraView.this.notifyAll();
|
||||
@@ -145,7 +145,7 @@ public class CameraView extends ViewGroup {
|
||||
@Override
|
||||
protected void onPreMain() {
|
||||
cameraToDestroy = camera;
|
||||
camera = Optional.absent();
|
||||
camera = Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
|
||||
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.util.ContextUtil;
|
||||
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.adapter.mapping.MappingModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -19,7 +20,6 @@ import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
||||
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.EmojiSource;
|
||||
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) {
|
||||
if (TextUtils.isEmpty(emoji)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
|
||||
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.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
@@ -141,7 +142,7 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
previousTransformationMethod = getTransformationMethod();
|
||||
|
||||
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 {
|
||||
CharSequence emojified = EmojiProvider.emojify(candidates, text, this, isJumbomoji || forceJumboEmoji);
|
||||
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)
|
||||
// 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) {
|
||||
ellipsizeAnyTextForMaxLength();
|
||||
} 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) {
|
||||
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
|
||||
|
||||
@@ -201,7 +213,7 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
int widthSpecMode = MeasureSpec.getMode(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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
return lastLineWidth;
|
||||
}
|
||||
@@ -291,7 +317,7 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
SpannableStringBuilder newContent = new SpannableStringBuilder();
|
||||
newContent.append(getText().subSequence(0, overflowStart))
|
||||
.append(ellipsized.subSequence(0, ellipsized.length()))
|
||||
.append(Optional.fromNullable(overflowText).or(""));
|
||||
.append(Optional.ofNullable(overflowText).orElse(""));
|
||||
|
||||
EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent);
|
||||
CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageButton;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -24,17 +26,17 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
|
||||
|
||||
public EmojiToggle(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
initialize(null);
|
||||
}
|
||||
|
||||
public EmojiToggle(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public EmojiToggle(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public void setToMedia() {
|
||||
@@ -45,11 +47,18 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
|
||||
setImageDrawable(imeToggle);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.emojiToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_emoji);
|
||||
private void initialize(@Nullable AttributeSet attrs) {
|
||||
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.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;
|
||||
|
||||
setToMedia();
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
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 java.util.HashSet;
|
||||
@@ -18,6 +18,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
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 String EMOJI_REGEX = "[^\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}\\p{Cf}\\p{Cs}\\s]";
|
||||
|
||||
private EmojiUtil() {}
|
||||
|
||||
@@ -84,4 +85,12 @@ public final class EmojiUtil {
|
||||
|
||||
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.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.res.TypedArrayUtils;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
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.KeyboardPagerFragment;
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemedFragment;
|
||||
|
||||
public class MediaKeyboard extends FrameLayout implements InputView {
|
||||
@@ -149,6 +146,10 @@ public class MediaKeyboard extends FrameLayout implements InputView {
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
public boolean isEmojiSearchMode() {
|
||||
return keyboardState == State.EMOJI_SEARCH;
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
if (!isInitialised) {
|
||||
|
||||
|
||||