Merge remote-tracking branch 'origin/dev' into closed_groups

# Conflicts:
#	.drone.jsonnet
#	app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt
#	app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
#	app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
#	app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
#	app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt
#	app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
#	app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt
#	app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java
#	app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt
#	app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
#	app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt
#	libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt
This commit is contained in:
SessionHero01
2024-09-11 14:24:48 +10:00
131 changed files with 2720 additions and 2606 deletions

View File

@@ -243,7 +243,8 @@ interface StorageProtocol {
fun ensureMessageHashesAreSender(hashes: Set<String>, sender: String, closedGroupId: String): Boolean
fun updateThread(threadId: Long, unarchive: Boolean)
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
fun insertMessageRequestResponse(response: MessageRequestResponse)
fun insertMessageRequestResponseFromContact(response: MessageRequestResponse)
fun insertMessageRequestResponseFromYou(threadId: Long)
fun setRecipientApproved(recipient: Recipient, approved: Boolean)
fun getRecipientApproved(address: Address): Boolean
fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean)

View File

@@ -4,5 +4,5 @@ enum class CallMessageType {
CALL_MISSED,
CALL_INCOMING,
CALL_OUTGOING,
CALL_FIRST_MISSED,
CALL_FIRST_MISSED
}

View File

@@ -278,7 +278,7 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? {
}
fun handleMessageRequestResponse(message: MessageRequestResponse) {
MessagingModuleConfiguration.shared.storage.insertMessageRequestResponse(message)
MessagingModuleConfiguration.shared.storage.insertMessageRequestResponseFromContact(message)
}
//endregion

View File

@@ -1,7 +1,6 @@
package org.session.libsession.messaging.utilities
import android.content.Context
import android.text.SpannableString
import com.squareup.phrase.Phrase
import org.session.libsession.R
import org.session.libsession.messaging.MessagingModuleConfiguration
@@ -26,7 +25,9 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.getExpirationTypeDisplayValue
import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.utilities.Log
object UpdateMessageBuilder {
const val TAG = "UpdateMessageBuilder"
@@ -49,7 +50,7 @@ object UpdateMessageBuilder {
// --- Group created or joined ---
is UpdateMessageData.Kind.GroupCreation -> {
if (!isOutgoing) {
context.getText(R.string.groupInviteYou)
context.getText(R.string.legacyGroupMemberYouNew)
} else {
"" // We no longer add a string like `disappearingMessagesNewGroup` ("You created a new group") and leave the group with its default empty state
}
@@ -75,20 +76,20 @@ object UpdateMessageBuilder {
return ""
}
1 -> {
Phrase.from(context, R.string.groupMemberNew)
.put(NAME_KEY, updateData.updatedMembers.elementAtOrNull(0))
Phrase.from(context, R.string.legacyGroupMemberNew)
.put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0)))
.format()
}
2 -> {
Phrase.from(context, R.string.groupMemberTwoNew)
.put(NAME_KEY, updateData.updatedMembers.elementAtOrNull(0))
.put(OTHER_NAME_KEY, updateData.updatedMembers.elementAtOrNull(1))
Phrase.from(context, R.string.legacyGroupMemberTwoNew)
.put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0)))
.put(OTHER_NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(1)))
.format()
}
else -> {
val newMemberCountMinusOne = newMemberCount - 1
Phrase.from(context, R.string.groupMemberMoreNew)
.put(NAME_KEY, updateData.updatedMembers.elementAtOrNull(0))
Phrase.from(context, R.string.legacyGroupMemberNewMultiple)
.put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0)))
.put(COUNT_KEY, newMemberCountMinusOne)
.format()
}
@@ -327,11 +328,19 @@ object UpdateMessageBuilder {
if (duration <= 0) {
// ..by you..
return if (isOutgoing) {
context.getText(R.string.disappearingMessagesTurnedOffYou)
// in a group
if(isGroup) context.getText(R.string.disappearingMessagesTurnedOffYouGroup)
// 1on1
else context.getText(R.string.disappearingMessagesTurnedOffYou)
}
else // ..or by someone else.
{
Phrase.from(context, R.string.disappearingMessagesTurnedOff)
Phrase.from(context,
// in a group
if(isGroup) R.string.disappearingMessagesTurnedOffGroup
// 1on1
else R.string.disappearingMessagesTurnedOff
)
.put(NAME_KEY, senderName)
.format()
}

View File

@@ -1,10 +1,12 @@
package org.session.libsession.utilities
// Non-translatable strings for UI substitutions
// Non-translatable strings for use with the UI
object NonTranslatableStringConstants {
const val APP_NAME = "Session"
const val DEBUG_MENU = "Debug Menu"
const val GIF = "GIF"
const val SESSION_DOWNLOAD_URL = "https://getsession.org/download"
const val WAVING_HAND_EMOJI = "\uD83D\uDC4B" // Used on the landing page & substituted into `onboardingBubbleWelcomeToSession`
const val BACKHAND_INDEX_POINTING_DOWN_EMOJI = "\uD83D\uDC47" // Used on the landing page & substituted into `onboardingBubbleCreatingAnAccountIsEasy`
}

View File

@@ -1,45 +1,111 @@
package org.session.libsession.utilities
import android.content.Context
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okio.Buffer
import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.messaging.file_server.FileServerApi
import org.session.libsignal.streams.ProfileCipherOutputStream
import org.session.libsignal.utilities.ProfileAvatarData
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLastProfilePictureUpload
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
import org.session.libsession.utilities.TextSecurePreferences.Companion.getProfileKey
import org.session.libsession.utilities.TextSecurePreferences.Companion.setLastProfilePictureUpload
import org.session.libsignal.streams.DigestingRequestBody
import org.session.libsignal.streams.ProfileCipherOutputStream
import org.session.libsignal.streams.ProfileCipherOutputStreamFactory
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ProfileAvatarData
import org.session.libsignal.utilities.ThreadUtils.queue
import org.session.libsignal.utilities.retryIfNeeded
import org.session.libsignal.utilities.ThreadUtils
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.*
object ProfilePictureUtilities {
fun upload(profilePicture: ByteArray, encodedProfileKey: String, context: Context): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
ThreadUtils.queue {
val inputStream = ByteArrayInputStream(profilePicture)
val outputStream = ProfileCipherOutputStream.getCiphertextLength(profilePicture.size.toLong())
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
val pad = ProfileAvatarData(inputStream, outputStream, "image/jpeg", ProfileCipherOutputStreamFactory(profileKey))
val drb = DigestingRequestBody(pad.data, pad.outputStreamFactory, pad.contentType, pad.dataLength, null)
val b = Buffer()
drb.writeTo(b)
val data = b.readByteArray()
var id: Long = 0
@OptIn(DelicateCoroutinesApi::class)
fun resubmitProfilePictureIfNeeded(context: Context) {
GlobalScope.launch(Dispatchers.IO) {
// Files expire on the file server after a while, so we simply re-upload the user's profile picture
// at a certain interval to ensure it's always available.
val userPublicKey = getLocalNumber(context) ?: return@launch
val now = Date().time
val lastProfilePictureUpload = getLastProfilePictureUpload(context)
if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return@launch
// Don't generate a new profile key here; we do that when the user changes their profile picture
Log.d("Loki-Avatar", "Uploading Avatar Started")
val encodedProfileKey =
getProfileKey(context)
try {
id = retryIfNeeded(4) {
FileServerApi.upload(data)
}.get()
// Read the file into a byte array
val inputStream = AvatarHelper.getInputStreamFor(
context,
fromSerialized(userPublicKey)
)
val baos = ByteArrayOutputStream()
var count: Int
val buffer = ByteArray(1024)
while ((inputStream.read(buffer, 0, buffer.size)
.also { count = it }) != -1
) {
baos.write(buffer, 0, count)
}
baos.flush()
val profilePicture = baos.toByteArray()
// Re-upload it
upload(
profilePicture,
encodedProfileKey!!,
context
)
// Update the last profile picture upload date
setLastProfilePictureUpload(
context,
Date().time
)
Log.d("Loki-Avatar", "Uploading Avatar Finished")
} catch (e: Exception) {
deferred.reject(e)
Log.e("Loki-Avatar", "Uploading avatar failed.")
}
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
val url = "${FileServerApi.server}/file/$id"
TextSecurePreferences.setProfilePictureURL(context, url)
deferred.resolve(Unit)
}
return deferred.promise
}
suspend fun upload(profilePicture: ByteArray, encodedProfileKey: String, context: Context) {
val inputStream = ByteArrayInputStream(profilePicture)
val outputStream =
ProfileCipherOutputStream.getCiphertextLength(profilePicture.size.toLong())
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
val pad = ProfileAvatarData(
inputStream,
outputStream,
"image/jpeg",
ProfileCipherOutputStreamFactory(profileKey)
)
val drb = DigestingRequestBody(
pad.data,
pad.outputStreamFactory,
pad.contentType,
pad.dataLength,
null
)
val b = Buffer()
drb.writeTo(b)
val data = b.readByteArray()
var id: Long = 0
// this can throw an error
id = retryIfNeeded(4) {
FileServerApi.upload(data)
}.get()
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
val url = "${FileServerApi.server}/file/$id"
TextSecurePreferences.setProfilePictureURL(context, url)
}
}

View File

@@ -4,6 +4,7 @@ package org.session.libsession.utilities
// Note: The substitution will be to {app_name} etc. in the strings - but do NOT include the curly braces in these keys!
object StringSubstitutionConstants {
const val ACCOUNT_ID_KEY = "account_id"
const val AUTHOR_KEY = "author"
const val APP_NAME_KEY = "app_name"
const val COMMUNITY_NAME_KEY = "community_name"
const val CONVERSATION_COUNT_KEY = "conversation_count"
@@ -17,6 +18,7 @@ object StringSubstitutionConstants {
const val GROUP_NAME_KEY = "group_name"
const val MEMBERS_KEY = "members"
const val MESSAGE_COUNT_KEY = "message_count"
const val MESSAGE_SNIPPET_KEY = "message_snippet"
const val NAME_KEY = "name"
const val OTHER_NAME_KEY = "other_name"
const val QUERY_KEY = "query"

File diff suppressed because it is too large Load Diff