mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 15:17:43 +00:00
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:
@@ -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)
|
||||
|
@@ -4,5 +4,5 @@ enum class CallMessageType {
|
||||
CALL_MISSED,
|
||||
CALL_INCOMING,
|
||||
CALL_OUTGOING,
|
||||
CALL_FIRST_MISSED,
|
||||
CALL_FIRST_MISSED
|
||||
}
|
@@ -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
|
||||
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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`
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
Reference in New Issue
Block a user