mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-06-18 05:05:44 +01:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5929866ae0 | |||
| d706fb0c4b | |||
| f4185d2868 | |||
| 9430c27e64 | |||
| b724f2b01a | |||
| 1e6d575ec9 | |||
| 4c7cf5212e | |||
| 33ca1132dc | |||
| a5e11abdc9 | |||
| 3924f65cbe | |||
| c500d8ecbd | |||
| cd98fd894d | |||
| 45a3c44d0c | |||
| 8ddec63e31 | |||
| 2d2a871194 | |||
| 2ef0032a33 | |||
| 570a310e2e | |||
| 930a263174 | |||
| 7df015ceef | |||
| 4c1555bc7b | |||
| e877f43dde | |||
| 52dcbb8bc6 | |||
| e0dd576cb1 | |||
| fb746b1ad5 | |||
| ef35efe34e | |||
| 6a30caff87 | |||
| cb2816362c | |||
| 5f67c9363e | |||
| ba76a8323e | |||
| aa9c7f7d7b | |||
| 411a0198b4 | |||
| 39679ebfc3 | |||
| 933b799266 | |||
| d22a2c0a50 | |||
| 3f682be609 | |||
| b16481616a | |||
| d44bef0eda | |||
| f02b8001e4 | |||
| fa258dcef2 | |||
| fc547218d1 | |||
| eea29813fa | |||
| 276d71d365 | |||
| 539276673a | |||
| d6871f8dc2 | |||
| d93543510f | |||
| 69f7ad28ec | |||
| 8c2ff2f1c2 |
@@ -1 +1,2 @@
|
||||
*.ai binary
|
||||
**/src/screenshotTest*/reference/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -20,6 +20,7 @@ jobs:
|
||||
# gh api repos/actions/checkout/commits/v6 --jq '.sha'
|
||||
with:
|
||||
submodules: true
|
||||
lfs: true
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
@@ -29,11 +30,11 @@ jobs:
|
||||
java-version: 17
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
|
||||
# gh api repos/gradle/actions/commits/v6 --jq '.sha'
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
|
||||
# gh api repos/gradle/actions/commits/v6 --jq '.sha'
|
||||
with:
|
||||
# Only 8.** branch builds write to the cache; everything else (PRs, etc.) reads only.
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
java-version: 17
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
|
||||
# gh api repos/gradle/actions/commits/v6 --jq '.sha'
|
||||
with:
|
||||
# PR-only workflow: always read from the cache, never write.
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${{ env.NDK_VERSION }}"
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
|
||||
# gh api repos/gradle/actions/commits/v6 --jq '.sha'
|
||||
|
||||
- name: Cache base apk
|
||||
|
||||
@@ -27,8 +27,8 @@ plugins {
|
||||
val staticIps = Properties().apply { file("static-ips.properties").reader().use { load(it) } }
|
||||
staticIps.stringPropertyNames().forEach { rootProject.extra[it] = staticIps.getProperty(it) }
|
||||
|
||||
val canonicalVersionCode = 1706
|
||||
val canonicalVersionName = "8.15.2"
|
||||
val canonicalVersionCode = 1708
|
||||
val canonicalVersionName = "8.16.0"
|
||||
val currentHotfixVersion = 0
|
||||
val maxHotfixVersions = 100
|
||||
|
||||
|
||||
+423
-36814
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.models.media.TransformProperties
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decodeBase64OrThrow
|
||||
@@ -27,7 +28,6 @@ import org.signal.core.util.copyTo
|
||||
import org.signal.core.util.stream.NullOutputStream
|
||||
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchivedMediaObject
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.network.api.AttachmentUploadResult
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import kotlin.random.Random
|
||||
|
||||
+1
-1
@@ -12,13 +12,13 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.models.media.TransformProperties
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.core.util.stream.LimitedInputStream
|
||||
import org.signal.core.util.update
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
@@ -19,16 +19,22 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.network.NetworkResult
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.backup.DeletionState
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.jobs.protos.BackupDeleteJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import java.util.UUID
|
||||
|
||||
class BackupDeleteJobTest {
|
||||
|
||||
@@ -155,10 +161,7 @@ class BackupDeleteJobTest {
|
||||
|
||||
@Test
|
||||
fun givenMediaOffloaded_whenIRun_thenIExpectAwaitingMediaDownload() {
|
||||
mockkObject(SignalDatabase)
|
||||
every { SignalDatabase.attachments.getRemainingRestorableAttachmentSize() } returns 1
|
||||
every { SignalDatabase.attachments.getOptimizedMediaAttachmentSize() } returns 1
|
||||
every { SignalDatabase.attachments.clearAllArchiveData() } returns Unit
|
||||
insertOffloadedAttachment()
|
||||
|
||||
SignalStore.backup.deletionState = DeletionState.CLEAR_LOCAL_STATE
|
||||
|
||||
@@ -252,4 +255,39 @@ class BackupDeleteJobTest {
|
||||
|
||||
assertThat(result.isRetry).isTrue()
|
||||
}
|
||||
|
||||
private fun insertOffloadedAttachment(size: Long = 100) {
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(
|
||||
mmsId = 1,
|
||||
attachments = listOf(
|
||||
PointerAttachment(
|
||||
contentType = "image/jpeg",
|
||||
transferState = AttachmentTable.TRANSFER_RESTORE_OFFLOADED,
|
||||
size = size,
|
||||
fileName = null,
|
||||
cdn = Cdn.CDN_3,
|
||||
location = "somelocation",
|
||||
key = Base64.encodeWithPadding(Util.getSecretBytes(64)),
|
||||
iv = null,
|
||||
digest = Util.getSecretBytes(64),
|
||||
incrementalDigest = null,
|
||||
incrementalMacChunkSize = 0,
|
||||
fastPreflightId = null,
|
||||
voiceNote = false,
|
||||
borderless = false,
|
||||
videoGif = false,
|
||||
width = 100,
|
||||
height = 100,
|
||||
uploadTimestamp = System.currentTimeMillis(),
|
||||
caption = null,
|
||||
stickerLocator = null,
|
||||
blurHash = null,
|
||||
uuid = UUID.randomUUID(),
|
||||
quote = false,
|
||||
quoteTargetContentType = null
|
||||
)
|
||||
),
|
||||
quoteAttachment = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -143,6 +143,7 @@ class BackupSubscriptionCheckJobTest {
|
||||
@Test
|
||||
fun givenUserIsNotRegistered_whenIRun_thenIExpectSuccessAndEarlyExit() {
|
||||
mockkObject(SignalStore.account) {
|
||||
every { SignalStore.account.e164 } returns "+15555550101"
|
||||
every { SignalStore.account.isRegistered } returns false
|
||||
|
||||
val job = BackupSubscriptionCheckJob.create()
|
||||
@@ -155,6 +156,7 @@ class BackupSubscriptionCheckJobTest {
|
||||
@Test
|
||||
fun givenIsLinkedDevice_whenIRun_thenIExpectSuccessAndEarlyExit() {
|
||||
mockkObject(SignalStore.account) {
|
||||
every { SignalStore.account.e164 } returns "+15555550101"
|
||||
every { SignalStore.account.isLinkedDevice } returns true
|
||||
|
||||
val job = BackupSubscriptionCheckJob.create()
|
||||
|
||||
@@ -241,5 +241,6 @@ class OtherClient(val serviceId: ServiceId, val e164: String, val identityKeyPai
|
||||
override fun deleteAllStaleOneTimeKyberPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
|
||||
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
|
||||
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
|
||||
override fun setMultiDevice(isMultiDevice: Boolean) = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ object AppCapabilities {
|
||||
versionedExpirationTimer = true,
|
||||
attachmentBackfill = true,
|
||||
spqr = true,
|
||||
usernameChangeSyncMessage = false // TODO(michelle): Turn on once all clients support it and add a migration
|
||||
usernameChangeSyncMessage = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,7 +600,18 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
||||
SelectedContact selectedContact = contact.requireSelectedContact();
|
||||
|
||||
if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId())) {
|
||||
boolean needsSelfCheck = !canSelectSelf && !selectedContact.hasUsername();
|
||||
|
||||
if (needsSelfCheck) {
|
||||
lifecycleDisposable.add(contactChipViewModel.isSelf(selectedContact)
|
||||
.subscribe(isSelf -> onItemClickResolved(contact, selectedContact, isUnknown, isSelf)));
|
||||
} else {
|
||||
onItemClickResolved(contact, selectedContact, isUnknown, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void onItemClickResolved(ContactSearchKey contact, SelectedContact selectedContact, boolean isUnknown, boolean isSelf) {
|
||||
if (isSelf) {
|
||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -561,6 +561,7 @@ class MainActivity :
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
BackHandler(paneExpansionState.currentAnchor == detailOnlyAnchor) {
|
||||
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Empty)
|
||||
scope.launch {
|
||||
paneExpansionState.animateTo(listOnlyAnchor)
|
||||
}
|
||||
|
||||
+10
-90
@@ -1,107 +1,27 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||
import org.thoughtcrime.securesms.conversation.NewConversationActivity;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Rfc5724Uri;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class SystemContactsEntrypointActivity extends Activity {
|
||||
|
||||
private static final String TAG = Log.tag(SystemContactsEntrypointActivity.class);
|
||||
public class SystemContactsEntrypointActivity extends ComponentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
startActivity(getNextIntent(getIntent()));
|
||||
finish();
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
private Intent getNextIntent(Intent original) {
|
||||
DestinationAndBody destination;
|
||||
SystemContactsEntrypointViewModel viewModel = new ViewModelProvider(this).get(SystemContactsEntrypointViewModel.class);
|
||||
|
||||
if (original.getData() != null && "content".equals(original.getData().getScheme())) {
|
||||
destination = getDestinationForSyncAdapter(original);
|
||||
} else {
|
||||
destination = getDestinationForView(original);
|
||||
}
|
||||
|
||||
final Intent nextIntent;
|
||||
|
||||
if (TextUtils.isEmpty(destination.destination)) {
|
||||
nextIntent = NewConversationActivity.createIntent(this, destination.getBody());
|
||||
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Recipient recipient = Recipient.external(destination.getDestination());
|
||||
|
||||
if (recipient != null) {
|
||||
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
|
||||
|
||||
nextIntent = ConversationIntents.createBuilderSync(this, recipient.getId(), threadId)
|
||||
.withDraftText(destination.getBody())
|
||||
.build();
|
||||
} else {
|
||||
nextIntent = NewConversationActivity.createIntent(this, destination.getBody());
|
||||
viewModel.getContactAction().observe(this, nextStep -> {
|
||||
if (nextStep.getShowSpecifyRecipientToast()) {
|
||||
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
return nextIntent;
|
||||
}
|
||||
startActivity(nextStep.getIntent());
|
||||
finish();
|
||||
});
|
||||
|
||||
private @NonNull DestinationAndBody getDestinationForView(Intent intent) {
|
||||
try {
|
||||
Rfc5724Uri smsUri = new Rfc5724Uri(intent.getData().toString());
|
||||
return new DestinationAndBody(smsUri.getPath(), smsUri.getQueryParams().get("body"));
|
||||
} catch (URISyntaxException e) {
|
||||
Log.w(TAG, "unable to parse RFC5724 URI from intent", e);
|
||||
return new DestinationAndBody("", "");
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull DestinationAndBody getDestinationForSyncAdapter(Intent intent) {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = getContentResolver().query(intent.getData(), null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return new DestinationAndBody(cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1)), "");
|
||||
}
|
||||
|
||||
return new DestinationAndBody("", "");
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DestinationAndBody {
|
||||
private final String destination;
|
||||
private final String body;
|
||||
|
||||
private DestinationAndBody(String destination, String body) {
|
||||
this.destination = destination;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
viewModel.resolveNextStep(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.thoughtcrime.securesms
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.ContactsContract
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.conversation.NewConversationActivity
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.Rfc5724Uri
|
||||
import java.net.URISyntaxException
|
||||
|
||||
class SystemContactsEntrypointViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(SystemContactsEntrypointViewModel::class.java)
|
||||
}
|
||||
|
||||
private val internalContactAction = MutableLiveData<ContactAction>()
|
||||
val contactAction: LiveData<ContactAction> = internalContactAction
|
||||
|
||||
fun resolveNextStep(original: Intent) {
|
||||
viewModelScope.launch {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
getContactAction(AppDependencies.application, original)
|
||||
}
|
||||
|
||||
internalContactAction.value = result
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getContactAction(context: Context, original: Intent): ContactAction {
|
||||
val destination = if (original.data != null && "content" == original.data?.scheme) {
|
||||
getDestinationForSyncAdapter(context, original)
|
||||
} else {
|
||||
getDestinationForView(original)
|
||||
}
|
||||
|
||||
val destinationAddress = destination.destination
|
||||
if (TextUtils.isEmpty(destinationAddress)) {
|
||||
return ContactAction(NewConversationActivity.createIntent(context, destination.body), true)
|
||||
}
|
||||
|
||||
val recipient = Recipient.external(destinationAddress!!)
|
||||
|
||||
if (recipient != null) {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
|
||||
val nextIntent = ConversationIntents.createBuilderSync(context, recipient.id, threadId)
|
||||
.withDraftText(destination.body)
|
||||
.build()
|
||||
|
||||
return ContactAction(nextIntent, false)
|
||||
}
|
||||
|
||||
return ContactAction(NewConversationActivity.createIntent(context, destination.body), true)
|
||||
}
|
||||
|
||||
private fun getDestinationForView(intent: Intent): DestinationAndBody {
|
||||
return try {
|
||||
val smsUri = Rfc5724Uri(intent.data.toString())
|
||||
DestinationAndBody(smsUri.path, smsUri.queryParams["body"])
|
||||
} catch (e: URISyntaxException) {
|
||||
Log.w(TAG, "unable to parse RFC5724 URI from intent", e)
|
||||
DestinationAndBody("", "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDestinationForSyncAdapter(context: Context, intent: Intent): DestinationAndBody {
|
||||
context.contentResolver.query(intent.data!!, null, null, null, null).use { cursor ->
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return DestinationAndBody(cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1)), "")
|
||||
}
|
||||
|
||||
return DestinationAndBody("", "")
|
||||
}
|
||||
}
|
||||
|
||||
data class ContactAction(
|
||||
val intent: Intent,
|
||||
val showSpecifyRecipientToast: Boolean
|
||||
)
|
||||
|
||||
private data class DestinationAndBody(
|
||||
val destination: String?,
|
||||
val body: String?
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import androidx.core.os.ParcelCompat
|
||||
import org.signal.blurhash.BlurHash
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.models.media.TransformProperties
|
||||
import org.signal.core.util.ParcelUtil
|
||||
import org.thoughtcrime.securesms.audio.AudioHash
|
||||
|
||||
@@ -7,9 +7,9 @@ import androidx.annotation.AnyThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.SingleSubject
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData
|
||||
|
||||
@@ -19,11 +19,11 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.bytes
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.throttleLatest
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.signal.core.util.Conversions;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.kdf.HKDF;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.backup.proto.Attachment;
|
||||
import org.thoughtcrime.securesms.backup.proto.Avatar;
|
||||
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.signal.core.util.SetUtil;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.backup.proto.KeyValue;
|
||||
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
|
||||
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
|
||||
|
||||
@@ -16,13 +16,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.bytes
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.safeUnregisterReceiver
|
||||
import org.signal.core.util.throttleLatest
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.RestoreState
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.signal.core.models.backup.BackupId
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.backup.MediaRootBackupKey
|
||||
import org.signal.core.models.backup.MessageBackupKey
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decodeBase64OrThrow
|
||||
import org.signal.core.util.CursorUtil
|
||||
@@ -72,9 +73,7 @@ import org.signal.network.NetworkResult
|
||||
import org.signal.network.StatusCodeErrorAction
|
||||
import org.signal.network.api.SvrBApi
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.signal.network.rest.toNetworkResult
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.whispersystems.signalservice.api.archive.BatchArchiveMediaResponse
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
|
||||
fun AttachmentTable.restoreWallpaperAttachment(attachment: Attachment): AttachmentId? {
|
||||
|
||||
+1
-1
@@ -42,6 +42,7 @@ import org.signal.archive.proto.Text
|
||||
import org.signal.archive.proto.ThreadMergeChatUpdate
|
||||
import org.signal.archive.proto.ViewOnceMessage
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.EventTimer
|
||||
import org.signal.core.util.Hex
|
||||
@@ -68,7 +69,6 @@ import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireLongOrNull
|
||||
import org.signal.core.util.requireString
|
||||
import org.signal.core.util.toByteArray
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupMode
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportOddities
|
||||
|
||||
+1
-1
@@ -7,10 +7,10 @@ package org.thoughtcrime.securesms.backup.v2.importer
|
||||
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.signal.archive.proto.Chat
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.toInt
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreWallpaperAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.util.parseChatWallpaper
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.signal.archive.stream.EncryptedBackupReader
|
||||
import org.signal.core.models.backup.BackupId
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.backup.MessageBackupKey
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.Util
|
||||
@@ -23,7 +24,6 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.core.util.toJson
|
||||
import org.signal.libsignal.crypto.Aes256Ctr32
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.LocalExportProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
|
||||
+1
-1
@@ -12,11 +12,11 @@ import org.signal.archive.proto.AccountData
|
||||
import org.signal.archive.proto.ChatStyle
|
||||
import org.signal.archive.proto.Frame
|
||||
import org.signal.archive.stream.BackupFrameEmitter
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.UuidUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.toByteArray
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportState
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
|
||||
@@ -7,8 +7,8 @@ package org.thoughtcrime.securesms.backup.v2.util
|
||||
|
||||
import org.signal.archive.proto.ChatStyle
|
||||
import org.signal.archive.proto.FilePointer
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupMode
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
|
||||
+1
-1
@@ -27,6 +27,7 @@ import org.signal.archive.stream.EncryptedBackupReader
|
||||
import org.signal.archive.stream.EncryptedBackupReader.Companion.MAC_SIZE
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.models.backup.MessageBackupKey
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.bytes
|
||||
@@ -39,7 +40,6 @@ import org.signal.core.util.stream.LimitedInputStream
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.signal.network.NetworkResult
|
||||
import org.signal.network.api.SvrBApi
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
import org.thoughtcrime.securesms.backup.LocalExportProgress
|
||||
|
||||
+20
-23
@@ -45,7 +45,6 @@ import org.signal.core.ui.compose.Texts
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.rememberStatusBarColorNestedScrollModifier
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
/**
|
||||
@@ -299,31 +298,29 @@ private fun AdvancedPrivacySettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (RemoteConfig.internalUser) {
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
val label = buildAnnotatedString {
|
||||
append(stringResource(R.string.preferences_automatic_key_verification_body))
|
||||
append(" ")
|
||||
withLink(
|
||||
LinkAnnotation.Clickable("learn-more", linkInteractionListener = {
|
||||
callbacks.onAutomaticVerificationLearnMoreClick()
|
||||
})
|
||||
) {
|
||||
append(stringResource(R.string.LearnMoreTextView_learn_more))
|
||||
}
|
||||
item {
|
||||
val label = buildAnnotatedString {
|
||||
append(stringResource(R.string.preferences_automatic_key_verification_body))
|
||||
append(" ")
|
||||
withLink(
|
||||
LinkAnnotation.Clickable("learn-more", linkInteractionListener = {
|
||||
callbacks.onAutomaticVerificationLearnMoreClick()
|
||||
})
|
||||
) {
|
||||
append(stringResource(R.string.LearnMoreTextView_learn_more))
|
||||
}
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = state.allowAutomaticKeyVerification,
|
||||
text = AnnotatedString(stringResource(R.string.preferences_automatic_key_verification)),
|
||||
label = label,
|
||||
onCheckChanged = callbacks::onAllowAutomaticVerificationChanged
|
||||
)
|
||||
}
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = state.allowAutomaticKeyVerification,
|
||||
text = AnnotatedString(stringResource(R.string.preferences_automatic_key_verification)),
|
||||
label = label,
|
||||
onCheckChanged = callbacks::onAllowAutomaticVerificationChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -42,6 +42,7 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import java.math.BigDecimal
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.ParseException
|
||||
import java.util.Currency
|
||||
import java.util.Optional
|
||||
|
||||
@@ -170,6 +171,8 @@ class DonateToSignalViewModel(
|
||||
decimalFormat.parse(amount) as BigDecimal
|
||||
} catch (e: NumberFormatException) {
|
||||
BigDecimal.ZERO
|
||||
} catch (e: ParseException) {
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -9,13 +9,17 @@ import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackPressedInfo
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackPressedInfoProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class ConversationSettingsNavHostFragment : NavHostFragment() {
|
||||
class ConversationSettingsNavHostFragment : NavHostFragment(), FragmentBackPressedInfoProvider {
|
||||
|
||||
companion object {
|
||||
suspend fun createArgs(recipientId: RecipientId): Bundle {
|
||||
@@ -36,4 +40,14 @@ class ConversationSettingsNavHostFragment : NavHostFragment() {
|
||||
navController.setGraph(R.navigation.conversation_settings, args)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun getFragmentBackPressedInfo(): Flow<FragmentBackPressedInfo> {
|
||||
return navController.currentBackStackEntryFlow.map {
|
||||
if (navController.previousBackStackEntry != null) {
|
||||
FragmentBackPressedInfo.Enabled { navController.popBackStack() }
|
||||
} else {
|
||||
FragmentBackPressedInfo.Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ object ConversationSettingsNavigator {
|
||||
recipient: Recipient
|
||||
) {
|
||||
if (activity is MainNavigationChatDetailRouter) {
|
||||
activity.goToChatDetail(MainNavigationDetailLocation.Chats.ConversationSettings(recipient.id))
|
||||
activity.goToChatDetail(MainNavigationDetailLocation.Chats.ConversationSettings(recipient.id, isContentRoot = true))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+14
-18
@@ -120,25 +120,13 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
}
|
||||
|
||||
if (event.type == PartProgressEvent.Type.COMPRESSION) {
|
||||
val mutableMap = it.compressionProgress.toMutableMap()
|
||||
val updateEvent = Progress.fromEvent(event)
|
||||
val existingEvent = mutableMap[attachment]
|
||||
if (existingEvent == null || updateEvent.completed > existingEvent.completed) {
|
||||
mutableMap[attachment] = updateEvent
|
||||
} else if (updateEvent.completed < 0.bytes) {
|
||||
mutableMap.remove(attachment)
|
||||
}
|
||||
return@updateState it.copy(compressionProgress = mutableMap.toMap())
|
||||
val progress = it.compressionProgress.toMutableMap()
|
||||
progress.applyProgress(attachment, Progress.fromEvent(event))
|
||||
return@updateState it.copy(compressionProgress = progress.toMap())
|
||||
} else {
|
||||
val mutableMap = it.networkProgress.toMutableMap()
|
||||
val updateEvent = Progress.fromEvent(event)
|
||||
val existingEvent = mutableMap[attachment]
|
||||
if (existingEvent == null || updateEvent.completed > existingEvent.completed) {
|
||||
mutableMap[attachment] = updateEvent
|
||||
} else if (updateEvent.completed < 0.bytes) {
|
||||
mutableMap.remove(attachment)
|
||||
}
|
||||
return@updateState it.copy(networkProgress = mutableMap.toMap())
|
||||
val progress = it.networkProgress.toMutableMap()
|
||||
progress.applyProgress(attachment, Progress.fromEvent(event))
|
||||
return@updateState it.copy(networkProgress = progress.toMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,6 +215,14 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
updateState { it.copy(isClickable = clickable) }
|
||||
}
|
||||
|
||||
private fun MutableMap<Attachment, Progress>.applyProgress(attachment: Attachment, update: Progress) {
|
||||
if (update.completed < 0.bytes) {
|
||||
remove(attachment)
|
||||
} else {
|
||||
put(attachment, update)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun verboseLog(message: () -> String) {
|
||||
if (VERBOSE_DEVELOPMENT_LOGGING) {
|
||||
Log.d(TAG, "[$viewId] ${message()}")
|
||||
|
||||
+3
-1
@@ -11,6 +11,8 @@ import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
|
||||
|
||||
/**
|
||||
* Pure, Android-View-free logic for the transfer controls UI.
|
||||
@@ -152,7 +154,7 @@ object TransferControls {
|
||||
} else if (state.isUpload) {
|
||||
ProgressLabel.Bytes(state.networkProgress.sumCompleted(), state.networkProgress.sumTotal())
|
||||
} else {
|
||||
val total = state.slides.sumOf { it.fileSize }.bytes
|
||||
val total = state.slides.sumOf { AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(it.fileSize)) }.bytes
|
||||
val completed = state.networkProgress.sumCompleted().let { if (it > total) total else it }
|
||||
ProgressLabel.Bytes(completed, total)
|
||||
}
|
||||
|
||||
+31
-9
@@ -65,7 +65,7 @@ fun TransferControls(
|
||||
is TransferControlsRenderState.Gone -> Unit
|
||||
|
||||
is TransferControlsRenderState.Pending -> Content(
|
||||
control = TransferProgressState.Ready(
|
||||
state = TransferProgressState.Ready(
|
||||
icon = arrowIcon(state.isUpload),
|
||||
startButtonContentDesc = startContentDescription(state.isUpload),
|
||||
startButtonOnClickLabel = startContentDescription(state.isUpload),
|
||||
@@ -79,7 +79,7 @@ fun TransferControls(
|
||||
)
|
||||
|
||||
is TransferControlsRenderState.Retry -> Content(
|
||||
control = TransferProgressState.Ready(
|
||||
state = TransferProgressState.Ready(
|
||||
icon = arrowIcon(state.isUpload),
|
||||
startButtonContentDesc = startContentDescription(state.isUpload),
|
||||
startButtonOnClickLabel = startContentDescription(state.isUpload),
|
||||
@@ -104,7 +104,7 @@ fun TransferControls(
|
||||
}
|
||||
|
||||
Content(
|
||||
control = TransferProgressState.InProgress(
|
||||
state = TransferProgressState.InProgress(
|
||||
progress = state.progress,
|
||||
cancelAction = if (state.cancelable) {
|
||||
TransferProgressState.InProgress.CancelAction(
|
||||
@@ -130,7 +130,7 @@ fun TransferControls(
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.Content(
|
||||
control: TransferProgressState,
|
||||
state: TransferProgressState,
|
||||
placement: TransferControls.Placement,
|
||||
showPlayButton: Boolean,
|
||||
centerLabel: String?,
|
||||
@@ -142,12 +142,16 @@ private fun BoxScope.Content(
|
||||
val controlInCorner = placement == TransferControls.Placement.CORNER
|
||||
|
||||
if (controlInCenter || showPlayButton || centerLabel != null) {
|
||||
val centerStartReadyState = if (controlInCenter) state as? TransferProgressState.Ready else null
|
||||
Pill(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
cornerRadius = 24.dp
|
||||
cornerRadius = 24.dp,
|
||||
onClick = centerStartReadyState?.onStartClick,
|
||||
onClickContentDescription = centerStartReadyState?.startButtonContentDesc,
|
||||
onClickLabel = centerStartReadyState?.startButtonOnClickLabel
|
||||
) {
|
||||
if (controlInCenter) {
|
||||
OnMediaIndicator(control, CENTER_CONTROL_SIZE)
|
||||
OnMediaIndicator(centerStartReadyState?.copy(onStartClick = null) ?: state, CENTER_CONTROL_SIZE)
|
||||
}
|
||||
|
||||
if (showPlayButton) {
|
||||
@@ -167,14 +171,18 @@ private fun BoxScope.Content(
|
||||
}
|
||||
|
||||
if (controlInCorner || cornerText != null) {
|
||||
val cornerStartReadyState = if (controlInCorner) state as? TransferProgressState.Ready else null
|
||||
Pill(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(4.dp),
|
||||
cornerRadius = 16.dp
|
||||
cornerRadius = 16.dp,
|
||||
onClick = cornerStartReadyState?.onStartClick,
|
||||
onClickContentDescription = cornerStartReadyState?.startButtonContentDesc,
|
||||
onClickLabel = cornerStartReadyState?.startButtonOnClickLabel
|
||||
) {
|
||||
if (controlInCorner) {
|
||||
OnMediaIndicator(control, 32.dp)
|
||||
OnMediaIndicator(cornerStartReadyState?.copy(onStartClick = null) ?: state, 32.dp)
|
||||
}
|
||||
|
||||
if (cornerText != null) {
|
||||
@@ -195,12 +203,26 @@ private fun BoxScope.Content(
|
||||
private fun Pill(
|
||||
modifier: Modifier = Modifier,
|
||||
cornerRadius: Dp,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onClickContentDescription: String? = null,
|
||||
onClickLabel: String? = null,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(cornerRadius))
|
||||
.background(colorResource(CoreUiR.color.signal_colorTransparentInverse4)),
|
||||
.background(colorResource(CoreUiR.color.signal_colorTransparentInverse4))
|
||||
.then(
|
||||
if (onClick != null) {
|
||||
Modifier.clickableContainer(
|
||||
contentDescription = onClickContentDescription,
|
||||
onClickLabel = onClickLabel ?: "",
|
||||
onClick = onClick
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
content()
|
||||
|
||||
+11
-7
@@ -84,10 +84,16 @@ private fun StartTransferButton(
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clickableContainer(
|
||||
contentDescription = state.startButtonContentDesc,
|
||||
onClickLabel = state.startButtonOnClickLabel,
|
||||
onClick = state.onStartClick
|
||||
.then(
|
||||
if (state.onStartClick != null) {
|
||||
Modifier.clickableContainer(
|
||||
contentDescription = state.startButtonContentDesc,
|
||||
onClickLabel = state.startButtonOnClickLabel,
|
||||
onClick = state.onStartClick
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
@@ -165,8 +171,6 @@ private fun ProgressIndicator(
|
||||
)
|
||||
}
|
||||
|
||||
// When cancelable, draw the filled "stop" square in the center of the ring (matches the legacy view's
|
||||
// IN_PROGRESS_CANCELABLE state). Sized as a fraction of the control so it scales with center/corner placements.
|
||||
if (state.cancelAction != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -198,7 +202,7 @@ sealed interface TransferProgressState {
|
||||
val icon: ImageVector,
|
||||
val startButtonContentDesc: String,
|
||||
val startButtonOnClickLabel: String,
|
||||
val onStartClick: () -> Unit
|
||||
val onStartClick: (() -> Unit)?
|
||||
) : TransferProgressState
|
||||
|
||||
data class InProgress(
|
||||
|
||||
+1
-2
@@ -8,7 +8,6 @@ import android.content.pm.ResolveInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -26,7 +25,7 @@ import androidx.media3.session.MediaSessionService;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
|
||||
+3
@@ -89,6 +89,7 @@ object CallInfoView {
|
||||
ParticipantsState(
|
||||
inCallLobby = state.callState == WebRtcViewModel.State.CALL_PRE_JOIN,
|
||||
ringGroup = state.ringGroup,
|
||||
isGroupOrAdHocCall = state.groupCallState.isNotIdle,
|
||||
includeSelf = state.groupCallState === WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED || state.groupCallState === WebRtcViewModel.GroupCallState.IDLE,
|
||||
participantCount = if (state.participantCount.isPresent) state.participantCount.get().toInt() else 0,
|
||||
remoteParticipants = state.allRemoteParticipants.sortedBy { it.callParticipantId.recipientId },
|
||||
@@ -345,6 +346,7 @@ private fun CallInfo(
|
||||
callParticipant = participant,
|
||||
isSelfAdmin = controlAndInfoState.isSelfAdmin(),
|
||||
isCallLink = controlAndInfoState.callLink != null,
|
||||
canRemoteMute = participantsState.isGroupOrAdHocCall,
|
||||
onDismiss = { selectedParticipant = null },
|
||||
onMuteAudio = onMuteAudio,
|
||||
onRemoveFromCall = onRemoveFromCall,
|
||||
@@ -702,6 +704,7 @@ private fun UnknownMembersRowPreview() {
|
||||
private data class ParticipantsState(
|
||||
val inCallLobby: Boolean = false,
|
||||
val ringGroup: Boolean = true,
|
||||
val isGroupOrAdHocCall: Boolean = false,
|
||||
val includeSelf: Boolean = false,
|
||||
val participantCount: Int = 0,
|
||||
val remoteParticipants: List<CallParticipant> = emptyList(),
|
||||
|
||||
+7
-2
@@ -46,6 +46,7 @@ fun ParticipantActionsSheet(
|
||||
callParticipant: CallParticipant,
|
||||
isSelfAdmin: Boolean,
|
||||
isCallLink: Boolean,
|
||||
canRemoteMute: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onMuteAudio: (CallParticipant) -> Unit,
|
||||
onRemoveFromCall: (CallParticipant) -> Unit,
|
||||
@@ -71,6 +72,7 @@ fun ParticipantActionsSheet(
|
||||
callParticipant = callParticipant,
|
||||
isSelfAdmin = isSelfAdmin,
|
||||
isCallLink = isCallLink,
|
||||
canRemoteMute = canRemoteMute,
|
||||
onDismiss = onDismiss,
|
||||
onMuteAudio = onMuteAudio,
|
||||
onRemoveFromCall = onRemoveFromCall,
|
||||
@@ -87,6 +89,7 @@ private fun ParticipantActionsSheetContent(
|
||||
callParticipant: CallParticipant,
|
||||
isSelfAdmin: Boolean,
|
||||
isCallLink: Boolean,
|
||||
canRemoteMute: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onMuteAudio: (CallParticipant) -> Unit,
|
||||
onRemoveFromCall: (CallParticipant) -> Unit,
|
||||
@@ -96,7 +99,7 @@ private fun ParticipantActionsSheetContent(
|
||||
) {
|
||||
ParticipantHeader(recipient = recipient)
|
||||
|
||||
if (callParticipant.isMicrophoneEnabled) {
|
||||
if (canRemoteMute && callParticipant.isMicrophoneEnabled) {
|
||||
Dividers.Default()
|
||||
|
||||
Rows.TextRow(
|
||||
@@ -110,7 +113,7 @@ private fun ParticipantActionsSheetContent(
|
||||
}
|
||||
|
||||
if (isSelfAdmin && isCallLink) {
|
||||
if (!callParticipant.isMicrophoneEnabled) {
|
||||
if (!(canRemoteMute && callParticipant.isMicrophoneEnabled)) {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
@@ -207,6 +210,7 @@ private fun ParticipantActionsSheetAdminPreview() {
|
||||
),
|
||||
isSelfAdmin = true,
|
||||
isCallLink = true,
|
||||
canRemoteMute = true,
|
||||
onDismiss = {},
|
||||
onMuteAudio = {},
|
||||
onRemoveFromCall = {},
|
||||
@@ -228,6 +232,7 @@ private fun ParticipantActionsSheetNonAdminPreview() {
|
||||
),
|
||||
isSelfAdmin = false,
|
||||
isCallLink = false,
|
||||
canRemoteMute = true,
|
||||
onDismiss = {},
|
||||
onMuteAudio = {},
|
||||
onRemoveFromCall = {},
|
||||
|
||||
@@ -72,6 +72,12 @@ class ContactChipViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun isSelf(selectedContact: SelectedContact): Single<Boolean> {
|
||||
return Single.fromCallable { Recipient.self().id == selectedContact.getOrCreateRecipientId() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
private fun getOrCreateRecipientId(selectedContact: SelectedContact): Single<RecipientId> {
|
||||
return Single.fromCallable {
|
||||
selectedContact.getOrCreateRecipientId()
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.signal.core.util.JsonUtils;
|
||||
|
||||
@@ -429,7 +429,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setGutterSizes(messageRecord, groupThread);
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper, isMessageRequestAccepted, allowedToPlayInline);
|
||||
setBodyText(messageRecord, searchQuery, isMessageRequestAccepted);
|
||||
setBodyText(messageRecord, searchQuery, isMessageRequestAccepted, hasWallpaper);
|
||||
setBubbleState(messageRecord, messageRecord.getFromRecipient(), hasWallpaper, colorizer);
|
||||
setInteractionState(conversationMessage, pulse);
|
||||
setStatusIcons(messageRecord, hasWallpaper);
|
||||
@@ -941,14 +941,22 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
footer.setRevealDotColor(colorizer.getOutgoingFooterIconColor(context));
|
||||
footer.setOnlyShowSendingStatus(false, messageRecord);
|
||||
} else if (messageRecord.isRemoteDelete()) {
|
||||
if (hasWallpaper) {
|
||||
bodyBubble.getBackground().setColorFilter(ContextCompat.getColor(context, R.color.wallpaper_bubble_color), PorterDuff.Mode.SRC_IN);
|
||||
if (messageRecord.isOutgoing() && hasWallpaper) {
|
||||
bodyBubble.getBackground().setColorFilter(recipient.getChatColors().getChatBubbleColorFilter());
|
||||
footer.setTextColor(colorizer.getOutgoingFooterTextColor(context));
|
||||
footer.setIconColor(colorizer.getOutgoingFooterIconColor(context));
|
||||
footer.setRevealDotColor(colorizer.getOutgoingFooterIconColor(context));
|
||||
} else if (hasWallpaper) {
|
||||
bodyBubble.getBackground().setColorFilter(getDefaultBubbleColor(true), PorterDuff.Mode.SRC_IN);
|
||||
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
footer.setIconColor(ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorNeutralVariantInverse));
|
||||
footer.setRevealDotColor(ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorNeutralVariantInverse));
|
||||
} else {
|
||||
bodyBubble.getBackground().setColorFilter(ContextCompat.getColor(context, R.color.signal_background_primary), PorterDuff.Mode.MULTIPLY);
|
||||
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
footer.setIconColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
footer.setRevealDotColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
}
|
||||
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
footer.setOnlyShowSendingStatus(messageRecord.isRemoteDelete(), messageRecord);
|
||||
} else {
|
||||
bodyBubble.getBackground().setColorFilter(getDefaultBubbleColor(hasWallpaper), PorterDuff.Mode.SRC_IN);
|
||||
@@ -1143,7 +1151,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
private void setBodyText(@NonNull MessageRecord messageRecord,
|
||||
@Nullable String searchQuery,
|
||||
boolean messageRequestAccepted)
|
||||
boolean messageRequestAccepted,
|
||||
boolean hasWallpaper)
|
||||
{
|
||||
bodyText.setClickable(false);
|
||||
bodyText.setFocusable(false);
|
||||
@@ -1154,14 +1163,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
bodyText.setMaxLength(-1);
|
||||
|
||||
if (RemoteConfig.receiveAdminDelete() && conversationMessage.getDeletedByRecipient() != null) {
|
||||
bodyText.setText(getDeletedMessageText(conversationMessage));
|
||||
bodyText.setText(getDeletedMessageText(conversationMessage, hasWallpaper));
|
||||
bodyText.setVisibility(View.VISIBLE);
|
||||
bodyText.setOverflowText(null);
|
||||
} else if (messageRecord.isRemoteDelete()) {
|
||||
String deletedMessage = context.getString(messageRecord.isOutgoing() ? R.string.ConversationItem_you_deleted_this_message : R.string.ConversationItem_this_message_was_deleted);
|
||||
SpannableString italics = new SpannableString(deletedMessage);
|
||||
italics.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, deletedMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
italics.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.signal_text_primary)),
|
||||
int textColor = messageRecord.isOutgoing() && hasWallpaper ? colorizer.getOutgoingDeleteTextColor(context)
|
||||
: ContextCompat.getColor(context, R.color.signal_text_primary);
|
||||
italics.setSpan(new ForegroundColorSpan(textColor),
|
||||
0,
|
||||
deletedMessage.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
@@ -1220,40 +1231,44 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private SpannableStringBuilder getDeletedMessageText(@NonNull ConversationMessage message) {
|
||||
private SpannableStringBuilder getDeletedMessageText(@NonNull ConversationMessage message, boolean hasWallpaper) {
|
||||
boolean isAdminDelete = !message.getDeletedByRecipient().equals(message.getMessageRecord().getFromRecipient());
|
||||
boolean useOutgoing = message.getMessageRecord().isOutgoing() && hasWallpaper;
|
||||
int textColor = useOutgoing ? colorizer.getOutgoingDeleteTextColor(context)
|
||||
: ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant);
|
||||
int nameColor = useOutgoing ? colorizer.getOutgoingDeleteNameColor(context)
|
||||
: colorizer.getIncomingGroupSenderColor(getContext(), message.getDeletedByRecipient());
|
||||
CharSequence body;
|
||||
|
||||
if (message.getDeletedByRecipient().equals(Recipient.self())) {
|
||||
body = formatDeletedText(context.getString(R.string.ConversationItem_you_deleted_this_message));
|
||||
body = formatDeletedText(context.getString(R.string.ConversationItem_you_deleted_this_message), textColor);
|
||||
} else if (!isAdminDelete) {
|
||||
body = formatDeletedText(context.getString(R.string.ConversationItem_s_deleted_this_message, message.getDeletedByRecipient().getShortDisplayName(context)));
|
||||
body = formatDeletedText(context.getString(R.string.ConversationItem_s_deleted_this_message, message.getDeletedByRecipient().getShortDisplayName(context)), textColor);
|
||||
} else {
|
||||
String template = context.getString(R.string.ConversationItem_admin_s_deleted_this_message, SpanUtil.SPAN_PLACE_HOLDER);
|
||||
int start = template.indexOf(SpanUtil.SPAN_PLACE_HOLDER);
|
||||
|
||||
int nameColor = colorizer.getIncomingGroupSenderColor(getContext(), message.getDeletedByRecipient());
|
||||
SpannableString name = new SpannableString(message.getDeletedByRecipient().getShortDisplayName(context));
|
||||
SpannableString name = new SpannableString(message.getDeletedByRecipient().getShortDisplayName(context));
|
||||
name.setSpan(new ForegroundColorSpan(nameColor), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
name.setSpan(new RecipientClickableSpan(conversationMessage.getDeletedByRecipient().getId()), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
name.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(template);
|
||||
builder.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant)), 0, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setSpan(new ForegroundColorSpan(textColor), 0, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.replace(start, start + SpanUtil.SPAN_PLACE_HOLDER.length(), name);
|
||||
|
||||
body = builder;
|
||||
}
|
||||
|
||||
return new SpannableStringBuilder()
|
||||
.append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.REGULAR, SignalSymbols.Glyph.X_CIRCLE, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant))
|
||||
.append(SpanUtil.color(textColor, SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.REGULAR, SignalSymbols.Glyph.X_CIRCLE, -1)))
|
||||
.append(" ")
|
||||
.append(body);
|
||||
}
|
||||
|
||||
private SpannableString formatDeletedText(String text) {
|
||||
private SpannableString formatDeletedText(String text, int textColor) {
|
||||
SpannableString spannableString = new SpannableString(text);
|
||||
spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant)), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannableString.setSpan(new ForegroundColorSpan(textColor), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return spannableString;
|
||||
}
|
||||
|
||||
@@ -2487,7 +2502,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
if ((messageRecord.isOutgoing() || !outgoingOnly) &&
|
||||
!hasNoBubble(messageRecord) &&
|
||||
!messageRecord.isRemoteDelete() &&
|
||||
(!messageRecord.isRemoteDelete() || (hasWallpaper && messageRecord.isOutgoing())) &&
|
||||
bodyBubbleCorners != null &&
|
||||
bodyBubble.getVisibility() == VISIBLE)
|
||||
{
|
||||
@@ -2954,10 +2969,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
messageRecord.getThreadId(),
|
||||
messageRecord.getTimestamp(),
|
||||
messageRecord.getId(),
|
||||
messageRecord.getFromRecipient().getId(),
|
||||
conversationRecipient.getId(),
|
||||
messageRecord.isOutgoing(),
|
||||
mediaUri,
|
||||
slide.getUri(),
|
||||
slide.getContentType(),
|
||||
slide.asAttachment().size,
|
||||
slide.getCaption().orElse(null),
|
||||
conversationMessage.getDisplayBody(getContext()),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ sealed class ConversationItemDisplayMode(val messageMode: MessageMode = MessageM
|
||||
object Starred : ConversationItemDisplayMode()
|
||||
|
||||
fun displayWallpaper(): Boolean {
|
||||
return this == Standard || this == Detailed
|
||||
return this == Standard
|
||||
}
|
||||
|
||||
enum class MessageMode {
|
||||
|
||||
+56
-80
@@ -94,6 +94,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
private TextView body;
|
||||
private MaterialButton actionButton;
|
||||
private View background;
|
||||
private View bodyBackground;
|
||||
private ConversationMessage conversationMessage;
|
||||
private Recipient conversationRecipient;
|
||||
private Optional<MessageRecord> previousMessageRecord;
|
||||
@@ -119,6 +120,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
private int latestFrame;
|
||||
private SpannableString displayBody;
|
||||
private ExpirationTimer timer;
|
||||
private boolean hasWallpaper;
|
||||
|
||||
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
|
||||
@@ -138,6 +140,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.actionButton = findViewById(R.id.conversation_update_action);
|
||||
this.background = findViewById(R.id.conversation_update_background);
|
||||
this.bodyBackground = findViewById(R.id.conversation_update_body_background);
|
||||
this.collapsedButton = findViewById(R.id.conversation_update_collapsed);
|
||||
|
||||
body.setOnClickListener(v -> {
|
||||
@@ -198,6 +201,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
this.nextMessageRecord = nextMessageRecord;
|
||||
this.conversationRecipient = conversationRecipient;
|
||||
this.isMessageRequestAccepted = isMessageRequestAccepted;
|
||||
this.hasWallpaper = hasWallpaper;
|
||||
|
||||
senderObserver.observe(lifecycleOwner, messageRecord.getFromRecipient());
|
||||
|
||||
@@ -209,9 +213,11 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
groupObserver.observe(lifecycleOwner, null);
|
||||
}
|
||||
|
||||
int textColor = ContextCompat.getColor(getContext(), R.color.conversation_item_update_text_color);
|
||||
if (ThemeUtil.isDarkTheme(getContext()) && hasWallpaper) {
|
||||
textColor = ContextCompat.getColor(getContext(), R.color.core_grey_15);
|
||||
int textColor;
|
||||
if (hasWallpaper) {
|
||||
textColor = ContextCompat.getColor(getContext(), org.signal.core.ui.R.color.signal_colorOnSurfaceVariant);
|
||||
} else {
|
||||
textColor = ContextCompat.getColor(getContext(), R.color.conversation_item_update_text_color);
|
||||
}
|
||||
|
||||
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext(), eventListener::onRecipientNameClicked));
|
||||
@@ -227,13 +233,10 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
|
||||
present(conversationMessage, nextMessageRecord, conversationRecipient, isMessageRequestAccepted);
|
||||
presentTimer(updateDescription);
|
||||
presentBackground(shouldCollapse(messageRecord, previousMessageRecord),
|
||||
shouldCollapse(messageRecord, nextMessageRecord),
|
||||
hasWallpaper,
|
||||
donationRequest);
|
||||
presentBackground(hasWallpaper, donationRequest);
|
||||
|
||||
presentActionButton(hasWallpaper, donationRequest);
|
||||
presentCollapsedHead(conversationMessage.getMessageRecord().getCollapsedState());
|
||||
presentCollapsedHead(hasWallpaper, conversationMessage.getMessageRecord().getCollapsedState());
|
||||
|
||||
updateSelectedState();
|
||||
}
|
||||
@@ -247,12 +250,11 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldCollapse(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> candidate)
|
||||
private static boolean isSameDayUpdate(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> candidate)
|
||||
{
|
||||
return candidate.isPresent() &&
|
||||
candidate.get().isUpdate() &&
|
||||
DateUtils.isSameDay(current.getTimestamp(), candidate.get().getTimestamp()) &&
|
||||
isSameType(current, candidate.get());
|
||||
DateUtils.isSameDay(current.getTimestamp(), candidate.get().getTimestamp());
|
||||
}
|
||||
|
||||
/** After a short delay, if the main data hasn't shown yet, then a loading message is displayed. */
|
||||
@@ -414,6 +416,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
|
||||
private void update() {
|
||||
present(conversationMessage, nextMessageRecord, conversationRecipient, isMessageRequestAccepted);
|
||||
presentBackground(hasWallpaper, conversationMessage.getMessageRecord().isReleaseChannelDonationRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,72 +794,43 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
(messageRecord.isGroupV2JoinRequest(toBlock.requireServiceId()) && previousMessageRecord.map(m -> m.isCollapsedGroupV2JoinUpdate(toBlock.requireServiceId())).orElse(false));
|
||||
}
|
||||
|
||||
private void presentBackground(boolean collapseAbove, boolean collapseBelow, boolean hasWallpaper, boolean isDonationRequest) {
|
||||
int marginDefault = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_vertical_margin);
|
||||
int marginCollapsed = 0;
|
||||
int paddingDefault = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_vertical_padding);
|
||||
int paddingCollapsed = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_vertical_padding_collapsed);
|
||||
private void presentBackground(boolean hasWallpaper, boolean isDonationRequest) {
|
||||
int marginCompact;
|
||||
int marginDefault;
|
||||
int topMargin;
|
||||
int bottomMargin;
|
||||
if (!hasWallpaper) {
|
||||
marginCompact = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_margin_compact);
|
||||
marginDefault = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_margin);
|
||||
} else {
|
||||
marginCompact = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_margin_compact_wallpaper);
|
||||
marginDefault = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_margin_wallpaper);
|
||||
}
|
||||
topMargin = isSameDayUpdate(messageRecord, previousMessageRecord) ? marginCompact : marginDefault;
|
||||
bottomMargin = isSameDayUpdate(messageRecord, nextMessageRecord) ? marginCompact : marginDefault;
|
||||
|
||||
if (collapseAbove && collapseBelow) {
|
||||
ViewUtil.setTopMargin(background, marginCollapsed);
|
||||
ViewUtil.setBottomMargin(background, marginCollapsed);
|
||||
int verticalPadding;
|
||||
if (actionButton.getVisibility() == View.VISIBLE) {
|
||||
verticalPadding = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_vertical_margin_action);
|
||||
} else if (hasWallpaper) {
|
||||
verticalPadding = getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_update_vertical_margin);
|
||||
} else {
|
||||
verticalPadding = 0;
|
||||
}
|
||||
|
||||
ViewUtil.setPaddingTop(background, paddingCollapsed);
|
||||
ViewUtil.setPaddingBottom(background, paddingCollapsed);
|
||||
ViewUtil.setTopMargin(background, topMargin);
|
||||
ViewUtil.setBottomMargin(background, bottomMargin);
|
||||
ViewUtil.setPaddingTop(bodyBackground, verticalPadding);
|
||||
ViewUtil.setPaddingBottom(bodyBackground, verticalPadding);
|
||||
|
||||
ViewUtil.updateLayoutParams(background, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (hasWallpaper) {
|
||||
background.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_middle);
|
||||
if (hasWallpaper && !conversationMessage.isActiveCollapsedHead()) {
|
||||
if (isDonationRequest) {
|
||||
bodyBackground.setBackgroundResource(R.drawable.conversation_update_release_note_background);
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
}
|
||||
} else if (collapseAbove) {
|
||||
ViewUtil.setTopMargin(background, marginCollapsed);
|
||||
ViewUtil.setBottomMargin(background, marginDefault);
|
||||
|
||||
ViewUtil.setPaddingTop(background, paddingDefault);
|
||||
ViewUtil.setPaddingBottom(background, paddingDefault);
|
||||
|
||||
ViewUtil.updateLayoutParams(background, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (hasWallpaper) {
|
||||
background.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_bottom);
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
}
|
||||
} else if (collapseBelow) {
|
||||
ViewUtil.setTopMargin(background, marginDefault);
|
||||
ViewUtil.setBottomMargin(background, marginCollapsed);
|
||||
|
||||
ViewUtil.setPaddingTop(background, paddingDefault);
|
||||
ViewUtil.setPaddingBottom(background, paddingCollapsed);
|
||||
|
||||
ViewUtil.updateLayoutParams(background, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (hasWallpaper) {
|
||||
background.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_top);
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
bodyBackground.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_singular);
|
||||
}
|
||||
} else {
|
||||
ViewUtil.setTopMargin(background, marginDefault);
|
||||
ViewUtil.setBottomMargin(background, marginDefault);
|
||||
|
||||
ViewUtil.setPaddingTop(background, paddingDefault);
|
||||
ViewUtil.setPaddingBottom(background, paddingDefault);
|
||||
|
||||
ViewUtil.updateLayoutParams(background, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (hasWallpaper) {
|
||||
if (isDonationRequest) {
|
||||
background.setBackgroundResource(R.drawable.conversation_update_release_note_background);
|
||||
} else {
|
||||
background.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_singular);
|
||||
}
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
}
|
||||
bodyBackground.setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,7 +847,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void presentCollapsedHead(CollapsedState collapsedState) {
|
||||
private void presentCollapsedHead(boolean hasWallpaper, CollapsedState collapsedState) {
|
||||
if (!conversationMessage.isActiveCollapsibleHead()) {
|
||||
collapsedButton.setVisibility(GONE);
|
||||
} else {
|
||||
@@ -898,6 +872,15 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
}
|
||||
});
|
||||
ViewUtil.setBottomMargin(collapsedButton, (int) DimensionUnit.DP.toPixels(conversationMessage.isActiveCollapsedHead() ? 0 : 12));
|
||||
|
||||
if (hasWallpaper) {
|
||||
collapsedButton.setBackgroundResource(R.drawable.conversation_update_wallpaper_background_singular);
|
||||
collapsedButton.setBackgroundTintList(null);
|
||||
} else {
|
||||
collapsedButton.setBackgroundResource(R.drawable.rounded_rectangle_38);
|
||||
collapsedButton.setBackgroundTintList(AppCompatResources.getColorStateList(getContext(), org.signal.core.ui.R.color.signal_colorSurface1));
|
||||
}
|
||||
|
||||
collapsedButton.setVisibility(VISIBLE);
|
||||
} else {
|
||||
Log.w(TAG, "Found a message that is a collapsible head but does not have a collapsible type.");
|
||||
@@ -925,14 +908,6 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isSameType(@NonNull MessageRecord current, @NonNull MessageRecord candidate) {
|
||||
return (current.isGroupUpdate() && candidate.isGroupUpdate()) ||
|
||||
(current.isProfileChange() && candidate.isProfileChange()) ||
|
||||
(current.isGroupCall() && candidate.isGroupCall()) ||
|
||||
(current.isExpirationTimerUpdate() && candidate.isExpirationTimerUpdate()) ||
|
||||
(current.isChangeNumber() && candidate.isChangeNumber());
|
||||
}
|
||||
|
||||
private void presentTimer(UpdateDescription updateDescription) {
|
||||
if (updateDescription.hasExpiration() && messageRecord.getExpiresIn() > 0 && messageRecord.getExpireStarted() > 0) {
|
||||
timer = new ExpirationTimer(messageRecord.getExpireStarted(), messageRecord.getExpiresIn());
|
||||
@@ -984,6 +959,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
if (recipient.getId() == conversationRecipient.getId() && (conversationRecipient == null || !conversationRecipient.hasSameContent(recipient))) {
|
||||
conversationRecipient = recipient;
|
||||
present(conversationMessage, nextMessageRecord, conversationRecipient, isMessageRequestAccepted);
|
||||
presentBackground(hasWallpaper, conversationMessage.getMessageRecord().isReleaseChannelDonationRequest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.ui.util.ThemeUtil
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -58,6 +59,24 @@ interface Colorizer {
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun getOutgoingDeleteTextColor(context: Context): Int {
|
||||
return if (ThemeUtil.isDarkTheme(context)) {
|
||||
ContextCompat.getColor(context, CoreUiR.color.signal_colorNeutralVariantInverse)
|
||||
} else {
|
||||
ContextCompat.getColor(context, CoreUiR.color.signal_colorNeutralVariant)
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun getOutgoingDeleteNameColor(context: Context): Int {
|
||||
return if (ThemeUtil.isDarkTheme(context)) {
|
||||
ContextCompat.getColor(context, CoreUiR.color.signal_colorNeutralInverse)
|
||||
} else {
|
||||
ContextCompat.getColor(context, CoreUiR.color.signal_colorNeutral)
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun getIncomingGroupSenderColor(context: Context, recipient: Recipient): Int {
|
||||
return getNameColor(context, recipient).getColor(context)
|
||||
|
||||
+1
-1
@@ -2975,7 +2975,7 @@ class ConversationFragment :
|
||||
ConversationDialogs.displayDeleteDialog(requireContext(), recipient) {
|
||||
messageRequestViewModel
|
||||
.onDelete()
|
||||
.doAfterSuccess { activity?.finish() }
|
||||
.doAfterSuccess { chatRouter.exitDetailLocation() }
|
||||
.subscribeWithShowProgress("delete message request")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,10 +11,10 @@ import android.text.Spanned
|
||||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import org.signal.core.util.addDetectedLinks
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan
|
||||
import org.thoughtcrime.securesms.util.LinkUtil
|
||||
import org.thoughtcrime.securesms.util.Linkification
|
||||
import org.thoughtcrime.securesms.util.UrlClickHandler
|
||||
import org.thoughtcrime.securesms.util.hasOnlyThumbnail
|
||||
|
||||
@@ -34,7 +34,7 @@ object V2ConversationItemUtils {
|
||||
}
|
||||
|
||||
LinkifyCompat.addLinks(messageBody, Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS)
|
||||
Linkification.applyWebUrlSpans(messageBody)
|
||||
messageBody.addDetectedLinks()
|
||||
|
||||
messageBody.getSpans(0, messageBody.length, URLSpan::class.java).forEach { urlSpan ->
|
||||
val url = urlSpan.url
|
||||
|
||||
+5
@@ -58,6 +58,11 @@ public class SignalServiceAccountDataStoreImpl implements SignalServiceAccountDa
|
||||
return SignalStore.account().isMultiDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMultiDevice(boolean isMultiDevice) {
|
||||
SignalStore.account().setMultiDevice(isMultiDevice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identityKeyStore.getIdentityKeyPair();
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.signal.archive.proto.BackupDebugInfo
|
||||
import org.signal.blurhash.BlurHash
|
||||
import org.signal.core.models.backup.MediaId
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.models.media.TransformProperties
|
||||
import org.signal.core.ui.util.StorageUtil
|
||||
import org.signal.core.util.Base64
|
||||
@@ -69,7 +70,6 @@ import org.signal.glide.decryptableuri.DecryptableUri
|
||||
import org.signal.network.api.AttachmentUploadResult
|
||||
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.LocalBackupKey
|
||||
|
||||
@@ -110,7 +110,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
)
|
||||
}
|
||||
fun markAllCallEventsRead(timestamp: Long = Long.MAX_VALUE) {
|
||||
val now = System.currentTimeMillis()
|
||||
val proposedExpireStarted = if (timestamp == Long.MAX_VALUE) System.currentTimeMillis() else timestamp
|
||||
|
||||
val allUnreadMissedCalls = readableDatabase
|
||||
.select(MESSAGE_ID)
|
||||
@@ -131,9 +131,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
|
||||
if (expiringCalls.isNotEmpty()) {
|
||||
Log.i(TAG, "Found ${expiringCalls.size} calls that needs expiring.")
|
||||
SignalDatabase.messages.markExpireStarted(expiringCalls.map { it.key to now })
|
||||
SignalDatabase.messages.markExpireStarted(expiringCalls.map { it.key to proposedExpireStarted })
|
||||
for ((messageId, expiresIn) in expiringCalls) {
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, now, expiresIn)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, proposedExpireStarted, expiresIn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,13 +143,13 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
}
|
||||
|
||||
fun markAllCallEventsWithPeerBeforeTimestampRead(peer: RecipientId, timestamp: Long): Call? {
|
||||
val now = System.currentTimeMillis()
|
||||
val proposedExpireStarted = if (timestamp == Long.MAX_VALUE) System.currentTimeMillis() else timestamp
|
||||
val latestCallAsOfTimestamp = writableDatabase.withinTransaction { db ->
|
||||
|
||||
val unreadMissedCalls = db
|
||||
.select(MESSAGE_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ? AND $READ != ? AND $EVENT = ?", peer.toLong(), timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED))
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ? AND $READ != ? AND $EVENT = ? AND $GROUP_CALL_ACTIVE = 0", peer.toLong(), timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED))
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
cursor.requireLong(MESSAGE_ID)
|
||||
@@ -164,9 +164,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
|
||||
if (expiring.isNotEmpty()) {
|
||||
Log.i(TAG, "Found ${expiring.size} calls that needs expiring.")
|
||||
SignalDatabase.messages.markExpireStarted(expiring.map { it.key to now })
|
||||
SignalDatabase.messages.markExpireStarted(expiring.map { it.key to proposedExpireStarted })
|
||||
for ((messageId, expiresIn) in expiring) {
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, now, expiresIn)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, proposedExpireStarted, expiresIn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,11 +196,11 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
.readToSingleLong()
|
||||
}
|
||||
|
||||
fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
|
||||
fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event, fromSync: Boolean = false) {
|
||||
val messageType: Long = Call.getMessageType(type, direction, event)
|
||||
|
||||
writableDatabase.withinTransaction {
|
||||
val result = SignalDatabase.messages.insertOneToOneCallLog(peer, messageType, timestamp, direction == Direction.OUTGOING)
|
||||
val result = SignalDatabase.messages.insertOneToOneCallLog(peer, messageType, timestamp, direction == Direction.OUTGOING, fromSync)
|
||||
val values = contentValuesOf(
|
||||
CALL_ID to callId,
|
||||
MESSAGE_ID to result.messageId,
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.core.content.contentValuesOf
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.TypeParceler
|
||||
import org.signal.core.util.DatabaseId
|
||||
import org.signal.core.models.database.DatabaseId
|
||||
import org.signal.core.util.DatabaseSerializer
|
||||
import org.signal.core.util.Serializer
|
||||
import org.signal.core.util.delete
|
||||
|
||||
@@ -152,7 +152,7 @@ object IssueReporter {
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().INTERNAL_ISSUES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] Issue detected")
|
||||
.setContentText("$name (${priority.label}). Please tap to get a debug log.")
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.core.util.JsonUtils
|
||||
@@ -67,7 +68,6 @@ import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment.DisplayOrderComparator
|
||||
import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter
|
||||
@@ -322,6 +322,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
private const val INDEX_NOTIFICATION_STATE = "message_notification_state_index"
|
||||
private const val INDEX_RATE_LIMITED = "message_rate_limited_index"
|
||||
private const val INDEX_SCHEDULED_NON_STORY = "message_scheduled_non_story_index"
|
||||
private const val INDEX_MESSAGE_PINNED_UNTIL = "message_pinned_until_index"
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXS = arrayOf(
|
||||
@@ -348,7 +349,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
// Partial index for marking messages read in a thread (see setMessagesReadSince). Only contains unread/unseen rows.
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_DATE_RECEIVED_UNREAD ON $TABLE_NAME ($THREAD_ID, $DATE_RECEIVED) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND ($READ = 0 OR $REACTIONS_UNREAD = 1 OR $VOTES_UNREAD = 1)",
|
||||
"CREATE INDEX IF NOT EXISTS message_votes_unread_index ON $TABLE_NAME ($VOTES_UNREAD)",
|
||||
"CREATE INDEX IF NOT EXISTS message_pinned_until_index ON $TABLE_NAME ($PINNED_UNTIL)",
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_MESSAGE_PINNED_UNTIL ON $TABLE_NAME ($PINNED_UNTIL)",
|
||||
"CREATE INDEX IF NOT EXISTS message_pinned_at_index ON $TABLE_NAME ($PINNED_AT)",
|
||||
"CREATE INDEX IF NOT EXISTS message_deleted_by_index ON $TABLE_NAME ($DELETED_BY)",
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_ARCHIVED_STORY ON $TABLE_NAME ($STORY_ARCHIVED, $STORY_TYPE, $DATE_SENT) WHERE $STORY_TYPE > 0 AND $STORY_ARCHIVED > 0",
|
||||
@@ -908,7 +909,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return results
|
||||
}
|
||||
|
||||
fun insertOneToOneCallLog(recipientId: RecipientId, type: Long, timestamp: Long, outgoing: Boolean): InsertResult {
|
||||
fun insertOneToOneCallLog(recipientId: RecipientId, type: Long, timestamp: Long, outgoing: Boolean, fromSync: Boolean = false): InsertResult {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val threadIdResult = threads.getOrCreateThreadIdResultFor(recipient.id, recipient.isGroup)
|
||||
val threadId = threadIdResult.threadId
|
||||
@@ -937,6 +938,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
notifyConversationListeners(threadId)
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
|
||||
// If inserting an outgoing call from a sync message, automatically start timer
|
||||
if (expiresIn != 0L && outgoing && fromSync) {
|
||||
Log.i(TAG, "Starting expiration timer after inserting a call from a sync message.")
|
||||
markExpireStarted(messageId, timestamp)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, timestamp, expiresIn)
|
||||
}
|
||||
|
||||
return InsertResult(
|
||||
messageId = messageId,
|
||||
threadId = threadId,
|
||||
@@ -1582,7 +1590,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.select(FROM_RECIPIENT_ID)
|
||||
.from(TABLE_NAME)
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_DATE_SENT_FROM_TO_THREAD")
|
||||
.where("$IS_STORY_CLAUSE AND $DATE_SENT IN ($timestamps) AND NOT ($outgoingTypeClause) AND $VIEWED_COLUMN > 0")
|
||||
.run()
|
||||
.readToList { cursor -> RecipientId.from(cursor.requireLong(FROM_RECIPIENT_ID)) }
|
||||
@@ -2179,7 +2187,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
where = "$THREAD_ID = ? AND $PINNED_UNTIL > 0",
|
||||
arguments = buildArgs(threadId),
|
||||
reverse = true,
|
||||
orderBy = if (orderByPinned) "$PINNED_AT ASC" else ""
|
||||
orderBy = if (orderByPinned) "$PINNED_AT ASC" else "",
|
||||
index = INDEX_MESSAGE_PINNED_UNTIL
|
||||
)
|
||||
|
||||
return mmsReaderFor(cursor).use { reader ->
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.DatabaseId;
|
||||
import org.signal.core.models.database.DatabaseId;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.signal.core.util.DatabaseId
|
||||
import org.signal.core.models.database.DatabaseId
|
||||
import org.signal.core.util.IntSerializer
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ import androidx.core.content.ContextCompat;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.CallTable;
|
||||
|
||||
+1
-1
@@ -399,7 +399,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
};
|
||||
|
||||
SignalWebSocket.AuthenticatedWebSocket webSocket = new SignalWebSocket.AuthenticatedWebSocket(authFactory,
|
||||
() -> !SignalStore.misc().isClientDeprecated() && !DeviceTransferBlockingInterceptor.getInstance().isBlockingNetwork() && !Environment.IS_INSTRUMENTATION,
|
||||
() -> !SignalStore.misc().isClientDeprecated() && SignalStore.account().isRegistered() && !TextSecurePreferences.isUnauthorizedReceived(context) && !DeviceTransferBlockingInterceptor.getInstance().isBlockingNetwork() && !Environment.IS_INSTRUMENTATION,
|
||||
sleepTimer,
|
||||
TimeUnit.SECONDS.toMillis(30));
|
||||
if (AppForegroundObserver.isForegrounded()) {
|
||||
|
||||
@@ -45,7 +45,6 @@ object JumboEmoji {
|
||||
private var currentVersion: Int = -1
|
||||
|
||||
@JvmStatic
|
||||
@MainThread
|
||||
fun updateCurrentVersion(context: Context) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val version: EmojiFiles.Version = EmojiFiles.Version.readVersion(context, true) ?: return@execute
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import com.bumptech.glide.load.data.StreamLocalUriFetcher;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.common.io.GlideStreamConfig;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
|
||||
@@ -11,7 +11,7 @@ import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import okio.ByteString
|
||||
import org.signal.core.util.DatabaseId
|
||||
import org.signal.core.models.database.DatabaseId
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.LRUCache
|
||||
import org.signal.core.util.Util
|
||||
|
||||
@@ -15,10 +15,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.text.util.LinkifyCompat;
|
||||
|
||||
import org.signal.core.util.LinkifierSpannableExtensionsKt;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.Linkification;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
|
||||
public final class GroupDescriptionUtil {
|
||||
@@ -38,7 +38,7 @@ public final class GroupDescriptionUtil {
|
||||
|
||||
if (linkify) {
|
||||
LinkifyCompat.addLinks(descriptionSpannable, Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
|
||||
Linkification.applyWebUrlSpans(descriptionSpannable);
|
||||
LinkifierSpannableExtensionsKt.addDetectedLinks(descriptionSpannable);
|
||||
|
||||
for (URLSpan urlSpan : descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class)) {
|
||||
String url = urlSpan.getURL();
|
||||
|
||||
@@ -296,7 +296,7 @@ class JobController {
|
||||
|
||||
@WorkerThread
|
||||
synchronized void onRetry(@NonNull Job job, long backoffInterval) {
|
||||
if (backoffInterval <= 0) {
|
||||
if (backoffInterval < 0) {
|
||||
throw new IllegalArgumentException("Invalid backoff interval! " + backoffInterval);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.glide.decryptableuri.DecryptableUri
|
||||
import org.signal.network.NetworkResult
|
||||
import org.signal.network.api.AttachmentUploadResult
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentUploadUtil
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveDatabaseExecutor
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.annotation.MainThread
|
||||
import okio.Source
|
||||
import okio.buffer
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.Util
|
||||
@@ -17,7 +18,6 @@ import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.signal.network.exceptions.PushNetworkException
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.drain
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.jobs
|
||||
import android.text.TextUtils
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
@@ -20,7 +21,6 @@ import org.signal.network.api.AttachmentUploadResult
|
||||
import org.signal.protos.resumableuploads.ResumableUpload
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentUploadUtil
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
@@ -87,10 +87,7 @@ class CheckKeyTransparencyJob private constructor(
|
||||
}
|
||||
|
||||
private fun canRunJob(): Boolean {
|
||||
return if (!RemoteConfig.internalUser) {
|
||||
Log.i(TAG, "Remote config is not on. Exiting.")
|
||||
false
|
||||
} else if (!SignalStore.account.isRegistered) {
|
||||
return if (!SignalStore.account.isRegistered) {
|
||||
Log.i(TAG, "Account not registered. Exiting.")
|
||||
false
|
||||
} else if (TextSecurePreferences.isUnauthorizedReceived(AppDependencies.application)) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider
|
||||
import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection
|
||||
@@ -34,8 +35,9 @@ class CheckServiceReachabilityJob private constructor(params: Parameters) : Base
|
||||
@JvmStatic
|
||||
fun enqueueIfNecessary() {
|
||||
val isCensored = AppDependencies.signalServiceNetworkAccess.isCensored()
|
||||
val context = AppDependencies.application
|
||||
val timeSinceLastCheck = System.currentTimeMillis() - SignalStore.misc.lastCensorshipServiceReachabilityCheckTime
|
||||
if (SignalStore.account.isRegistered && isCensored && timeSinceLastCheck > TimeUnit.DAYS.toMillis(1)) {
|
||||
if (SignalStore.account.isRegistered && !TextSecurePreferences.isUnauthorizedReceived(context) && isCensored && timeSinceLastCheck > TimeUnit.DAYS.toMillis(1)) {
|
||||
AppDependencies.jobManager.add(CheckServiceReachabilityJob())
|
||||
}
|
||||
}
|
||||
@@ -56,6 +58,12 @@ class CheckServiceReachabilityJob private constructor(params: Parameters) : Base
|
||||
return
|
||||
}
|
||||
|
||||
if (TextSecurePreferences.isUnauthorizedReceived(context)) {
|
||||
Log.w(TAG, "Unauthorized received, skipping.")
|
||||
SignalStore.misc.lastCensorshipServiceReachabilityCheckTime = System.currentTimeMillis()
|
||||
return
|
||||
}
|
||||
|
||||
if (!AppDependencies.signalServiceNetworkAccess.isCensored()) {
|
||||
Log.w(TAG, "Not currently censored, skipping.")
|
||||
SignalStore.misc.lastCensorshipServiceReachabilityCheckTime = System.currentTimeMillis()
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64.decodeBase64
|
||||
import org.signal.core.util.ByteSize
|
||||
import org.signal.core.util.bytes
|
||||
@@ -9,7 +10,6 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.logW
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.network.NetworkResult
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.concurrent.safeBlockingGet
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.audio.AudioWaveForms
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
@@ -75,6 +75,7 @@ import org.thoughtcrime.securesms.migrations.EmojiSearchIndexCheckMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.FixChangeNumberErrorMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.GooglePlayBillingPurchaseTokenMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.IdentityTableCleanupMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.KeyTransparencyUsernameMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
||||
import org.thoughtcrime.securesms.migrations.OptimizeMessageSearchIndexMigrationJob;
|
||||
@@ -340,6 +341,7 @@ public final class JobManagerFactories {
|
||||
put(FixChangeNumberErrorMigrationJob.KEY, new FixChangeNumberErrorMigrationJob.Factory());
|
||||
put(GooglePlayBillingPurchaseTokenMigrationJob.KEY, new GooglePlayBillingPurchaseTokenMigrationJob.Factory());
|
||||
put(IdentityTableCleanupMigrationJob.KEY, new IdentityTableCleanupMigrationJob.Factory());
|
||||
put(KeyTransparencyUsernameMigrationJob.KEY, new KeyTransparencyUsernameMigrationJob.Factory());
|
||||
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
|
||||
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
||||
put(OptimizeMessageSearchIndexMigrationJob.KEY, new OptimizeMessageSearchIndexMigrationJob.Factory());
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.net.Uri
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireLong
|
||||
@@ -14,7 +15,6 @@ import org.signal.core.util.requireString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.glide.decryptableuri.DecryptableUri
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.CONTENT_TYPE
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_FILE
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64.decodeBase64OrThrow
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
@@ -22,7 +23,6 @@ import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.signal.network.exceptions.PushNetworkException
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveDatabaseExecutor
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveDatabaseExecutor
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
|
||||
@@ -6,13 +6,13 @@ package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.net.Uri
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.androidx.DocumentFileInfo
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.InvalidMacException
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.local.ArchiveFileSystem
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.models.backup.MediaName
|
||||
import org.signal.core.models.database.AttachmentId
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decodeBase64
|
||||
import org.signal.core.util.Util
|
||||
@@ -17,7 +18,6 @@ import org.signal.network.NetworkResult
|
||||
import org.signal.network.api.AttachmentUploadResult
|
||||
import org.signal.protos.resumableuploads.ResumableUpload
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentUploadUtil
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.keyboard.emoji
|
||||
|
||||
import android.view.KeyEvent
|
||||
|
||||
/**
|
||||
* Mapping of [EmojiKeyboardCallback] methods into a sealed event class
|
||||
*/
|
||||
sealed interface EmojiKeyboardEvent {
|
||||
object OpenEmojiSearch : EmojiKeyboardEvent
|
||||
object CloseEmojiSearch : EmojiKeyboardEvent
|
||||
data class EmojiInsert(val emoji: String?) : EmojiKeyboardEvent
|
||||
data class EmojiKeyEvent(val keyEvent: KeyEvent?) : EmojiKeyboardEvent
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.keyboard.emoji
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Glue ViewModel that allows a component to dispatch emoji events to subcomponents.
|
||||
*/
|
||||
class EmojiKeyboardEventViewModel : ViewModel() {
|
||||
|
||||
private val eventChannel = Channel<EmojiKeyboardEvent>(Channel.BUFFERED)
|
||||
val events: Flow<EmojiKeyboardEvent> = eventChannel.receiveAsFlow()
|
||||
|
||||
fun onEvent(event: EmojiKeyboardEvent) {
|
||||
viewModelScope.launch {
|
||||
eventChannel.send(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.signal.core.util.JsonUtils;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.util.stream.Collectors;
|
||||
import org.signal.core.util.Linkifier;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.Linkification;
|
||||
import org.signal.core.util.Util;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
|
||||
@@ -51,7 +50,7 @@ public final class LinkPreviewUtil {
|
||||
* @return All URLs allowed as previews in the source text.
|
||||
*/
|
||||
public static @NonNull Links findValidPreviewUrls(@NonNull String text) {
|
||||
List<Linkifier.DetectedLink> detected = Linkification.findWebLinks(text);
|
||||
List<Linkifier.DetectedLink> detected = Linkifier.findLinks(text);
|
||||
if (detected.isEmpty()) {
|
||||
return Links.EMPTY;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.linkpreview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -12,7 +11,7 @@ import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.RequestController;
|
||||
|
||||
@@ -25,7 +25,6 @@ public class SignalPinReminders {
|
||||
private static final long FOUR_WEEKS = TimeUnit.DAYS.toMillis(28);
|
||||
|
||||
private static final NavigableSet<Long> INTERVALS = new TreeSet<Long>() {{
|
||||
add(ONE_DAY);
|
||||
add(THREE_DAYS);
|
||||
add(ONE_WEEK);
|
||||
add(TWO_WEEKS);
|
||||
|
||||
@@ -200,7 +200,7 @@ public abstract class BaseSvrPinFragment<ViewModel extends BaseSvrPinViewModel>
|
||||
}
|
||||
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.show(requireContext(), false, () -> {
|
||||
PinOptOutDialog.show(requireContext(), getViewLifecycleOwner(), false, () -> {
|
||||
RegistrationUtil.maybeMarkRegistrationComplete();
|
||||
closeNavGraphBranch();
|
||||
});
|
||||
|
||||
@@ -125,6 +125,6 @@ public final class SvrSplashFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.show(requireContext(), false, () -> requireActivity().finish());
|
||||
PinOptOutDialog.show(requireContext(), getViewLifecycleOwner(), false, () -> requireActivity().finish());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ final class LogSectionNotifications implements LogSection {
|
||||
.append("New contact alerts : ").append(SignalStore.settings().isNotifyWhenContactJoinsSignal()).append("\n")
|
||||
.append("In-chat sounds : ").append(SignalStore.settings().isMessageNotificationsInChatSoundsEnabled()).append("\n")
|
||||
.append("Repeat alerts : ").append(SignalStore.settings().getMessageNotificationsRepeatAlerts()).append("\n")
|
||||
.append("Notification display : ").append(SignalStore.settings().getMessageNotificationsPrivacy()).append("\n\n");
|
||||
.append("Notification display : ").append(SignalStore.settings().getMessageNotificationsPrivacy()).append("\n")
|
||||
.append("Force websocket : ").append(SignalStore.settings().getForceWebsocketMode()).append("\n\n");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
NotificationManager manager = ServiceUtil.getNotificationManager(context);
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.thoughtcrime.securesms.components.ConversationSearchBottomBar;
|
||||
import org.thoughtcrime.securesms.components.ProgressCard;
|
||||
import org.thoughtcrime.securesms.components.SearchView;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Linkification;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.signal.core.ui.util.ThemeUtil;
|
||||
@@ -458,7 +457,7 @@ public class SubmitDebugLogActivity extends BaseActivity {
|
||||
TextView dialogView = new TextView(builder.getContext());
|
||||
LongClickCopySpan longClickUrl = new LongClickCopySpan(url);
|
||||
|
||||
for (Linkifier.DetectedLink link : Linkification.findWebLinks(dialogText)) {
|
||||
for (Linkifier.DetectedLink link : Linkifier.findLinks(dialogText)) {
|
||||
spannableDialogText.setSpan(longClickUrl, link.getStart(), link.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.longmessage;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@@ -14,16 +15,19 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
class LongMessage {
|
||||
|
||||
private final ConversationMessage conversationMessage;
|
||||
private final CharSequence fullBody;
|
||||
|
||||
LongMessage(@NonNull ConversationMessage conversationMessage) {
|
||||
@WorkerThread
|
||||
LongMessage(@NonNull ConversationMessage conversationMessage, @NonNull Context context) {
|
||||
this.conversationMessage = conversationMessage;
|
||||
this.fullBody = conversationMessage.getDisplayBody(context);
|
||||
}
|
||||
|
||||
@NonNull MessageRecord getMessageRecord() {
|
||||
return conversationMessage.getMessageRecord();
|
||||
}
|
||||
|
||||
@NonNull CharSequence getFullBody(@NonNull Context context) {
|
||||
return conversationMessage.getDisplayBody(context);
|
||||
@NonNull CharSequence getFullBody() {
|
||||
return fullBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,16 +122,17 @@ public class LongMessageFragment extends FullScreenDialogFragment {
|
||||
EmojiTextView text = bubble.findViewById(R.id.longmessage_text);
|
||||
ConversationItemFooter footer = bubble.findViewById(R.id.longmessage_footer);
|
||||
|
||||
SpannableString body = new SpannableString(getTrimmedBody(message.get().getFullBody(requireContext())));
|
||||
SpannableString body = new SpannableString(getTrimmedBody(message.get().getFullBody()));
|
||||
V2ConversationItemUtils.linkifyUrlLinks(body,
|
||||
true,
|
||||
url -> CommunicationActions.handlePotentialGroupLinkUrl(requireActivity(), url) ||
|
||||
CommunicationActions.handlePotentialProxyLinkUrl(requireActivity(), url));
|
||||
|
||||
bubble.setVisibility(View.VISIBLE);
|
||||
text.setMovementMethod(LongClickMovementMethod.getInstance(getContext()));
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, SignalStore.settings().getMessageFontSize());
|
||||
text.setTextAsync(body);
|
||||
text.setTextIsSelectable(true);
|
||||
text.setMovementMethod(LongClickMovementMethod.getInstance(getContext()));
|
||||
|
||||
if (!message.get().getMessageRecord().isOutgoing()) {
|
||||
text.setMentionBackgroundTint(ContextCompat.getColor(requireContext(), ThemeUtil.isDarkTheme(requireActivity()) ? R.color.core_grey_60 : R.color.core_grey_20));
|
||||
|
||||
@@ -37,7 +37,7 @@ class LongMessageRepository {
|
||||
Optional<MmsMessageRecord> record = getMmsMessage(mmsDatabase, messageId);
|
||||
if (record.isPresent()) {
|
||||
final ConversationMessage resolvedMessage = LongMessageResolveerKt.resolveBody(record.get(), context);
|
||||
return Optional.of(new LongMessage(resolvedMessage));
|
||||
return Optional.of(new LongMessage(resolvedMessage, context));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -197,6 +197,9 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
}
|
||||
|
||||
arguments?.let { args ->
|
||||
val backPressedState = remember { FragmentBackPressedState() }
|
||||
FragmentBackHandler(backPressedState)
|
||||
|
||||
AndroidFragment(
|
||||
clazz = ConversationSettingsNavHostFragment::class.java,
|
||||
fragmentState = fragmentState,
|
||||
@@ -206,7 +209,9 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.statusBarsPadding()
|
||||
.navigationBarsPadding()
|
||||
)
|
||||
) { fragment ->
|
||||
backPressedState.attach(fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,10 @@ sealed interface MainNavigationDetailLocation : Parcelable {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ConversationSettings(val recipientId: RecipientId) : Chats {
|
||||
data class ConversationSettings(
|
||||
val recipientId: RecipientId,
|
||||
override val isContentRoot: Boolean = false
|
||||
) : Chats {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: RecipientId = recipientId
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user