mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Merge remote-tracking branch 'upstream/dev' into disappearing-messages
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt # app/src/main/res/layout/activity_conversation_v2_action_bar.xml
This commit is contained in:
commit
7f7971e47f
@ -1,3 +1,4 @@
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
@ -13,6 +14,11 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'kotlin-kapt'
|
||||
id 'com.google.dagger.hilt.android'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'witness'
|
||||
@ -22,11 +28,16 @@ apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
|
||||
|
||||
configurations.all {
|
||||
exclude module: "commons-logging"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("com.google.dagger:hilt-android:2.46.1")
|
||||
kapt("com.google.dagger:hilt-android-compiler:2.44")
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation "com.google.android.material:material:$materialVersion"
|
||||
@ -39,7 +50,6 @@ dependencies {
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.4'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
|
||||
@ -154,6 +164,16 @@ dependencies {
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.4'
|
||||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||
|
||||
implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.1'
|
||||
implementation 'androidx.compose.ui:ui:1.4.3'
|
||||
implementation 'androidx.compose.ui:ui-tooling:1.4.3'
|
||||
implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.31.5-beta"
|
||||
implementation "com.google.accompanist:accompanist-pager-indicators:0.31.5-beta"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.4.3"
|
||||
|
||||
implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02'
|
||||
implementation 'androidx.compose.material:material:1.5.0-alpha02'
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 354
|
||||
@ -199,6 +219,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.4.7'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode canonicalVersionCode * postFixSize
|
||||
versionName canonicalVersionName
|
||||
@ -305,3 +332,8 @@ def autoResConfig() {
|
||||
.collect { matcher -> matcher.group(1) }
|
||||
.sort()
|
||||
}
|
||||
|
||||
// Allow references to generated code
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
@ -147,6 +147,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
}
|
||||
};
|
||||
|
||||
public static Intent getPreviewIntent(Context context, MediaPreviewArgs args) {
|
||||
return getPreviewIntent(context, args.getSlide(), args.getMmsRecord(), args.getThread());
|
||||
}
|
||||
|
||||
public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms, Recipient threadRecipient) {
|
||||
Intent previewIntent = null;
|
||||
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||
@ -524,7 +528,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
|
||||
if (data != null) {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent);
|
||||
mediaPager.setAdapter(adapter);
|
||||
adapter.setActive(true);
|
||||
|
@ -0,0 +1,11 @@
|
||||
package org.thoughtcrime.securesms
|
||||
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
|
||||
data class MediaPreviewArgs(
|
||||
val slide: Slide,
|
||||
val mmsRecord: MmsMessageRecord?,
|
||||
val thread: Recipient?,
|
||||
)
|
@ -249,18 +249,12 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
viewModel.callState.collect { state ->
|
||||
Log.d("Loki", "Consuming view model state $state")
|
||||
when (state) {
|
||||
CALL_RINGING -> {
|
||||
if (wantsToAnswer) {
|
||||
answerCall()
|
||||
wantsToAnswer = false
|
||||
}
|
||||
}
|
||||
CALL_OUTGOING -> {
|
||||
}
|
||||
CALL_CONNECTED -> {
|
||||
CALL_RINGING -> if (wantsToAnswer) {
|
||||
answerCall()
|
||||
wantsToAnswer = false
|
||||
}
|
||||
else -> { /* do nothing */ }
|
||||
CALL_CONNECTED -> wantsToAnswer = false
|
||||
else -> {}
|
||||
}
|
||||
updateControls(state)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
@ -9,6 +10,7 @@ import androidx.annotation.DimenRes
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
||||
import network.loki.messenger.databinding.ViewUserBinding
|
||||
import org.session.libsession.avatars.ContactColors
|
||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||
import org.session.libsession.avatars.ProfileContactPhoto
|
||||
@ -18,13 +20,14 @@ import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class ProfilePictureView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
private val binding: ViewProfilePictureBinding by lazy { ViewProfilePictureBinding.bind(this) }
|
||||
lateinit var glide: GlideRequests
|
||||
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
|
||||
private val glide: GlideRequests = GlideApp.with(this)
|
||||
var publicKey: String? = null
|
||||
var displayName: String? = null
|
||||
var additionalPublicKey: String? = null
|
||||
@ -37,8 +40,13 @@ class ProfilePictureView @JvmOverloads constructor(
|
||||
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
|
||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) }
|
||||
|
||||
|
||||
// endregion
|
||||
|
||||
constructor(context: Context, sender: Recipient): this(context) {
|
||||
update(sender)
|
||||
}
|
||||
|
||||
// region Updating
|
||||
fun update(recipient: Recipient) {
|
||||
fun getUserDisplayName(publicKey: String): String {
|
||||
@ -80,7 +88,6 @@ class ProfilePictureView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (!this::glide.isInitialized) return
|
||||
val publicKey = publicKey ?: return
|
||||
val additionalPublicKey = additionalPublicKey
|
||||
if (additionalPublicKey != null) {
|
||||
|
@ -55,8 +55,7 @@ class UserView : LinearLayout {
|
||||
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
|
||||
}
|
||||
val address = user.address.serialize()
|
||||
binding.profilePictureView.root.glide = glide
|
||||
binding.profilePictureView.root.update(user)
|
||||
binding.profilePictureView.update(user)
|
||||
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
|
||||
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
|
||||
when (actionIndicator) {
|
||||
@ -88,7 +87,7 @@ class UserView : LinearLayout {
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
binding.profilePictureView.root.recycle()
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
@ -32,14 +32,13 @@ class ContactListAdapter(
|
||||
|
||||
class ContactViewHolder(private val binding: ViewContactBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(contact: ContactListItem.Contact, glide: GlideRequests, listener: (Recipient) -> Unit) {
|
||||
binding.profilePictureView.root.glide = glide
|
||||
binding.profilePictureView.root.update(contact.recipient)
|
||||
binding.profilePictureView.update(contact.recipient)
|
||||
binding.nameTextView.text = contact.displayName
|
||||
binding.root.setOnClickListener { listener(contact.recipient) }
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
binding.profilePictureView.root.recycle()
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,8 @@ import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.text.set
|
||||
import androidx.core.text.toSpannable
|
||||
@ -108,6 +110,10 @@ import org.thoughtcrime.securesms.conversation.ConversationActionBarDelegate
|
||||
import org.thoughtcrime.securesms.conversation.expiration.ExpirationSettingsActivity
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.SendSeedDialog
|
||||
@ -1904,10 +1910,24 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
endActionMode()
|
||||
}
|
||||
|
||||
private val handleMessageDetail = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
val message = result.data?.extras?.getLong(MESSAGE_TIMESTAMP)
|
||||
?.let(mmsSmsDb::getMessageForTimestamp)
|
||||
|
||||
val set = setOfNotNull(message)
|
||||
|
||||
when (result.resultCode) {
|
||||
ON_REPLY -> reply(set)
|
||||
ON_RESEND -> resendMessage(set)
|
||||
ON_DELETE -> deleteMessages(set)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showMessageDetail(messages: Set<MessageRecord>) {
|
||||
val intent = Intent(this, MessageDetailActivity::class.java)
|
||||
intent.putExtra(MessageDetailActivity.MESSAGE_TIMESTAMP, messages.first().timestamp)
|
||||
push(intent)
|
||||
Intent(this, MessageDetailActivity::class.java)
|
||||
.apply { putExtra(MESSAGE_TIMESTAMP, messages.first().timestamp) }
|
||||
.let { handleMessageDetail.launch(it) }
|
||||
|
||||
endActionMode()
|
||||
}
|
||||
|
||||
@ -1946,7 +1966,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
|
||||
override fun reply(messages: Set<MessageRecord>) {
|
||||
val recipient = viewModel.recipient ?: return
|
||||
binding?.inputBar?.draftQuote(recipient, messages.first(), glide)
|
||||
messages.firstOrNull()?.let { binding?.inputBar?.draftQuote(recipient, it, glide) }
|
||||
endActionMode()
|
||||
}
|
||||
|
||||
|
@ -695,9 +695,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
|
||||
items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.conversation_context__menu_ban_and_delete_all), () -> handleActionItemClicked(Action.BAN_AND_DELETE_ALL)));
|
||||
}
|
||||
// Message detail
|
||||
if (message.isFailed()) {
|
||||
items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO)));
|
||||
}
|
||||
items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO)));
|
||||
// Resend
|
||||
if (message.isFailed()) {
|
||||
items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND)));
|
||||
|
@ -1,99 +1,401 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent.ACTION_UP
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||
import com.bumptech.glide.integration.compose.GlideImage
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityMessageDetailBinding
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.ExpirationUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity.getPreviewIntent
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import org.thoughtcrime.securesms.ui.AppTheme
|
||||
import org.thoughtcrime.securesms.ui.Avatar
|
||||
import org.thoughtcrime.securesms.ui.CarouselNextButton
|
||||
import org.thoughtcrime.securesms.ui.CarouselPrevButton
|
||||
import org.thoughtcrime.securesms.ui.Cell
|
||||
import org.thoughtcrime.securesms.ui.CellNoMargin
|
||||
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
|
||||
import org.thoughtcrime.securesms.ui.Divider
|
||||
import org.thoughtcrime.securesms.ui.GetString
|
||||
import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
|
||||
import org.thoughtcrime.securesms.ui.ItemButton
|
||||
import org.thoughtcrime.securesms.ui.PreviewTheme
|
||||
import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
|
||||
import org.thoughtcrime.securesms.ui.TitledText
|
||||
import org.thoughtcrime.securesms.ui.blackAlpha40
|
||||
import org.thoughtcrime.securesms.ui.colorDestructive
|
||||
import org.thoughtcrime.securesms.ui.destructiveButtonColors
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
|
||||
private lateinit var binding: ActivityMessageDetailBinding
|
||||
var messageRecord: MessageRecord? = null
|
||||
class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var storage: Storage
|
||||
|
||||
// region Settings
|
||||
private val viewModel: MessageDetailsViewModel by viewModels()
|
||||
|
||||
companion object {
|
||||
// Extras
|
||||
const val MESSAGE_TIMESTAMP = "message_timestamp"
|
||||
|
||||
const val ON_REPLY = 1
|
||||
const val ON_RESEND = 2
|
||||
const val ON_DELETE = 3
|
||||
}
|
||||
// endregion
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
binding = ActivityMessageDetailBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = resources.getString(R.string.conversation_context__menu_message_details)
|
||||
val timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
|
||||
// We only show this screen for messages fail to send,
|
||||
// so the author of the messages must be the current user.
|
||||
val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
|
||||
messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
val threadId = messageRecord!!.threadId
|
||||
val openGroup = storage.getOpenGroup(threadId)
|
||||
val blindedKey = openGroup?.let { group ->
|
||||
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null
|
||||
val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase())
|
||||
if (blindingEnabled) {
|
||||
SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes
|
||||
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
|
||||
} else null
|
||||
}
|
||||
updateContent()
|
||||
binding.resendButton.setOnClickListener {
|
||||
ResendMessageUtilities.resend(this, messageRecord!!, blindedKey)
|
||||
finish()
|
||||
|
||||
viewModel.timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
|
||||
|
||||
ComposeView(this)
|
||||
.apply { setContent { MessageDetailsScreen() } }
|
||||
.let(::setContentView)
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.eventFlow.collect {
|
||||
when (it) {
|
||||
Event.Finish -> finish()
|
||||
is Event.StartMediaPreview -> startActivity(
|
||||
getPreviewIntent(this@MessageDetailActivity, it.args)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateContent() {
|
||||
val dateLocale = Locale.getDefault()
|
||||
val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
|
||||
binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
|
||||
|
||||
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId())
|
||||
if (errorMessage != null) {
|
||||
binding.errorMessage.text = errorMessage
|
||||
binding.resendContainer.isVisible = true
|
||||
binding.errorContainer.isVisible = true
|
||||
} else {
|
||||
binding.errorContainer.isVisible = false
|
||||
binding.resendContainer.isVisible = false
|
||||
}
|
||||
|
||||
if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
|
||||
binding.expiresContainer.visibility = View.GONE
|
||||
} else {
|
||||
binding.expiresContainer.visibility = View.VISIBLE
|
||||
val elapsed = SnodeAPI.nowWithOffset - messageRecord!!.expireStarted
|
||||
val remaining = messageRecord!!.expiresIn - elapsed
|
||||
|
||||
val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1))
|
||||
binding.expiresIn.text = duration
|
||||
@Composable
|
||||
private fun MessageDetailsScreen() {
|
||||
val state by viewModel.stateFlow.collectAsState()
|
||||
AppTheme {
|
||||
MessageDetails(
|
||||
state = state,
|
||||
onReply = { setResultAndFinish(ON_REPLY) },
|
||||
onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } },
|
||||
onDelete = { setResultAndFinish(ON_DELETE) },
|
||||
onClickImage = { viewModel.onClickImage(it) },
|
||||
onAttachmentNeedsDownload = viewModel::onAttachmentNeedsDownload,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setResultAndFinish(code: Int) {
|
||||
Bundle().apply { putLong(MESSAGE_TIMESTAMP, viewModel.timestamp) }
|
||||
.let(Intent()::putExtras)
|
||||
.let { setResult(code, it) }
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Composable
|
||||
fun MessageDetails(
|
||||
state: MessageDetailsState,
|
||||
onReply: () -> Unit = {},
|
||||
onResend: (() -> Unit)? = null,
|
||||
onDelete: () -> Unit = {},
|
||||
onClickImage: (Int) -> Unit = {},
|
||||
onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> }
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
state.record?.let { message ->
|
||||
AndroidView(
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
factory = {
|
||||
ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply {
|
||||
bind(
|
||||
message,
|
||||
thread = state.thread!!,
|
||||
onAttachmentNeedsDownload = onAttachmentNeedsDownload,
|
||||
suppressThumbnails = true
|
||||
)
|
||||
|
||||
setOnTouchListener { _, event ->
|
||||
if (event.actionMasked == ACTION_UP) onContentClick(event)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Carousel(state.imageAttachments) { onClickImage(it) }
|
||||
state.nonImageAttachmentFileDetails?.let { FileDetails(it) }
|
||||
CellMetadata(state)
|
||||
CellButtons(
|
||||
onReply,
|
||||
onResend,
|
||||
onDelete,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CellMetadata(
|
||||
state: MessageDetailsState,
|
||||
) {
|
||||
state.apply {
|
||||
if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return
|
||||
CellWithPaddingAndMargin {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
TitledText(sent)
|
||||
TitledText(received)
|
||||
TitledErrorText(error)
|
||||
senderInfo?.let {
|
||||
TitledView(state.fromTitle) {
|
||||
Row {
|
||||
sender?.let { Avatar(it) }
|
||||
TitledMonospaceText(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CellButtons(
|
||||
onReply: () -> Unit = {},
|
||||
onResend: (() -> Unit)? = null,
|
||||
onDelete: () -> Unit = {},
|
||||
) {
|
||||
Cell {
|
||||
Column {
|
||||
ItemButton(
|
||||
stringResource(R.string.reply),
|
||||
R.drawable.ic_message_details__reply,
|
||||
onClick = onReply
|
||||
)
|
||||
Divider()
|
||||
onResend?.let {
|
||||
ItemButton(
|
||||
stringResource(R.string.resend),
|
||||
R.drawable.ic_message_details__refresh,
|
||||
onClick = it
|
||||
)
|
||||
Divider()
|
||||
}
|
||||
ItemButton(
|
||||
stringResource(R.string.delete),
|
||||
R.drawable.ic_message_details__trash,
|
||||
colors = destructiveButtonColors(),
|
||||
onClick = onDelete
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
|
||||
if (attachments.isEmpty()) return
|
||||
|
||||
val pagerState = rememberPagerState { attachments.size }
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Row {
|
||||
CarouselPrevButton(pagerState)
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
CellCarousel(pagerState, attachments, onClick)
|
||||
HorizontalPagerIndicator(pagerState)
|
||||
ExpandButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(8.dp)
|
||||
) { onClick(pagerState.currentPage) }
|
||||
}
|
||||
CarouselNextButton(pagerState)
|
||||
}
|
||||
attachments.getOrNull(pagerState.currentPage)?.fileDetails?.let { FileDetails(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalFoundationApi::class,
|
||||
ExperimentalGlideComposeApi::class
|
||||
)
|
||||
@Composable
|
||||
private fun CellCarousel(
|
||||
pagerState: PagerState,
|
||||
attachments: List<Attachment>,
|
||||
onClick: (Int) -> Unit
|
||||
) {
|
||||
CellNoMargin {
|
||||
HorizontalPager(state = pagerState) { i ->
|
||||
GlideImage(
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.clickable { onClick(i) },
|
||||
model = attachments[i].uri,
|
||||
contentDescription = attachments[i].fileName ?: stringResource(id = R.string.image)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = blackAlpha40,
|
||||
modifier = modifier,
|
||||
contentColor = Color.White,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_expand),
|
||||
contentDescription = stringResource(id = R.string.expand),
|
||||
modifier = Modifier.clickable { onClick() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewMessageDetails(
|
||||
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
||||
) {
|
||||
PreviewTheme(themeResId) {
|
||||
MessageDetails(
|
||||
state = MessageDetailsState(
|
||||
nonImageAttachmentFileDetails = listOf(
|
||||
TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"),
|
||||
TitledText(R.string.message_details_header__file_type, "image/png"),
|
||||
TitledText(R.string.message_details_header__file_size, "195.6kB"),
|
||||
TitledText(R.string.message_details_header__resolution, "342x312"),
|
||||
),
|
||||
sent = TitledText(R.string.message_details_header__sent, "6:12 AM Tue, 09/08/2022"),
|
||||
received = TitledText(R.string.message_details_header__received, "6:12 AM Tue, 09/08/2022"),
|
||||
error = TitledText(R.string.message_details_header__error, "Message failed to send"),
|
||||
senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun FileDetails(fileDetails: List<TitledText>) {
|
||||
if (fileDetails.isEmpty()) return
|
||||
|
||||
CellWithPaddingAndMargin(padding = 0.dp) {
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(vertical = 24.dp, horizontal = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
fileDetails.forEach {
|
||||
BoxWithConstraints {
|
||||
TitledText(
|
||||
it,
|
||||
modifier = Modifier
|
||||
.widthIn(min = maxWidth.div(2))
|
||||
.padding(horizontal = 12.dp)
|
||||
.width(IntrinsicSize.Max)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitledErrorText(titledText: TitledText?) {
|
||||
TitledText(
|
||||
titledText,
|
||||
valueStyle = LocalTextStyle.current.copy(color = colorDestructive)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitledMonospaceText(titledText: TitledText?) {
|
||||
TitledText(
|
||||
titledText,
|
||||
valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitledText(
|
||||
titledText: TitledText?,
|
||||
modifier: Modifier = Modifier,
|
||||
valueStyle: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
titledText?.apply {
|
||||
TitledView(title, modifier) {
|
||||
Text(text, style = valueStyle, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitledView(title: GetString, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
|
||||
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Title(title)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Title(title: GetString) {
|
||||
Text(title.string(), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.MediaPreviewArgs
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiMessageDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.ui.GetString
|
||||
import org.thoughtcrime.securesms.ui.TitledText
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MessageDetailsViewModel @Inject constructor(
|
||||
private val attachmentDb: AttachmentDatabase,
|
||||
private val lokiMessageDatabase: LokiMessageDatabase,
|
||||
private val mmsSmsDatabase: MmsSmsDatabase,
|
||||
private val threadDb: ThreadDatabase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val state = MutableStateFlow(MessageDetailsState())
|
||||
val stateFlow = state.asStateFlow()
|
||||
|
||||
private val event = Channel<Event>()
|
||||
val eventFlow = event.receiveAsFlow()
|
||||
|
||||
var timestamp: Long = 0L
|
||||
set(value) {
|
||||
field = value
|
||||
val record = mmsSmsDatabase.getMessageForTimestamp(timestamp)
|
||||
|
||||
if (record == null) {
|
||||
viewModelScope.launch { event.send(Event.Finish) }
|
||||
return
|
||||
}
|
||||
|
||||
val mmsRecord = record as? MmsMessageRecord
|
||||
|
||||
state.value = record.run {
|
||||
val slides = mmsRecord?.slideDeck?.slides ?: emptyList()
|
||||
|
||||
MessageDetailsState(
|
||||
attachments = slides.map(::Attachment),
|
||||
record = record,
|
||||
sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) },
|
||||
received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) },
|
||||
error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) },
|
||||
senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } },
|
||||
sender = individualRecipient,
|
||||
thread = threadDb.getRecipientForThreadId(threadId)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val Slide.details: List<TitledText>
|
||||
get() = listOfNotNull(
|
||||
fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) },
|
||||
TitledText(R.string.message_details_header__file_type, asAttachment().contentType),
|
||||
TitledText(R.string.message_details_header__file_size, Util.getPrettyFileSize(fileSize)),
|
||||
takeIf { it is ImageSlide }
|
||||
?.let(Slide::asAttachment)
|
||||
?.run { "${width}x$height" }
|
||||
?.let { TitledText(R.string.message_details_header__resolution, it) },
|
||||
attachmentDb.duration(this)?.let { TitledText(R.string.message_details_header__duration, it) },
|
||||
)
|
||||
|
||||
private fun AttachmentDatabase.duration(slide: Slide): String? =
|
||||
slide.takeIf { it.hasAudio() }
|
||||
?.run { asAttachment() as? DatabaseAttachment }
|
||||
?.run { getAttachmentAudioExtras(attachmentId)?.durationMs }
|
||||
?.takeIf { it > 0 }
|
||||
?.let {
|
||||
String.format(
|
||||
"%01d:%02d",
|
||||
TimeUnit.MILLISECONDS.toMinutes(it),
|
||||
TimeUnit.MILLISECONDS.toSeconds(it) % 60
|
||||
)
|
||||
}
|
||||
|
||||
fun Attachment(slide: Slide): Attachment =
|
||||
Attachment(slide.details, slide.fileName.orNull(), slide.uri, slide is ImageSlide)
|
||||
|
||||
fun onClickImage(index: Int) {
|
||||
val state = state.value ?: return
|
||||
val mmsRecord = state.mmsRecord ?: return
|
||||
val slide = mmsRecord.slideDeck.slides[index] ?: return
|
||||
// only open to downloaded images
|
||||
if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) {
|
||||
// Restart download here (on IO thread)
|
||||
(slide.asAttachment() as? DatabaseAttachment)?.let { attachment ->
|
||||
onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord.getId())
|
||||
}
|
||||
}
|
||||
|
||||
if (slide.isInProgress) return
|
||||
|
||||
viewModelScope.launch {
|
||||
MediaPreviewArgs(slide, state.mmsRecord, state.thread)
|
||||
.let(Event::StartMediaPreview)
|
||||
.let { event.send(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MessageDetailsState(
|
||||
val attachments: List<Attachment> = emptyList(),
|
||||
val imageAttachments: List<Attachment> = attachments.filter { it.hasImage },
|
||||
val nonImageAttachmentFileDetails: List<TitledText>? = attachments.firstOrNull { !it.hasImage }?.fileDetails,
|
||||
val record: MessageRecord? = null,
|
||||
val mmsRecord: MmsMessageRecord? = record as? MmsMessageRecord,
|
||||
val sent: TitledText? = null,
|
||||
val received: TitledText? = null,
|
||||
val error: TitledText? = null,
|
||||
val senderInfo: TitledText? = null,
|
||||
val sender: Recipient? = null,
|
||||
val thread: Recipient? = null,
|
||||
) {
|
||||
val fromTitle = GetString(R.string.message_details_header__from)
|
||||
}
|
||||
|
||||
data class Attachment(
|
||||
val fileDetails: List<TitledText>,
|
||||
val fileName: String?,
|
||||
val uri: Uri?,
|
||||
val hasImage: Boolean
|
||||
)
|
||||
|
||||
sealed class Event {
|
||||
object Finish: Event()
|
||||
data class StartMediaPreview(val args: MediaPreviewArgs): Event()
|
||||
}
|
@ -28,11 +28,10 @@ class MentionCandidateView : LinearLayout {
|
||||
|
||||
private fun update() = with(binding) {
|
||||
mentionCandidateNameTextView.text = mentionCandidate.displayName
|
||||
profilePictureView.root.publicKey = mentionCandidate.publicKey
|
||||
profilePictureView.root.displayName = mentionCandidate.displayName
|
||||
profilePictureView.root.additionalPublicKey = null
|
||||
profilePictureView.root.glide = glide!!
|
||||
profilePictureView.root.update()
|
||||
profilePictureView.publicKey = mentionCandidate.publicKey
|
||||
profilePictureView.displayName = mentionCandidate.displayName
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.update()
|
||||
if (openGroupServer != null && openGroupRoom != null) {
|
||||
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
|
||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||
|
@ -28,11 +28,10 @@ class MentionCandidateView : RelativeLayout {
|
||||
|
||||
private fun update() = with(binding) {
|
||||
mentionCandidateNameTextView.text = candidate.displayName
|
||||
profilePictureView.root.publicKey = candidate.publicKey
|
||||
profilePictureView.root.displayName = candidate.displayName
|
||||
profilePictureView.root.additionalPublicKey = null
|
||||
profilePictureView.root.glide = glide!!
|
||||
profilePictureView.root.update()
|
||||
profilePictureView.publicKey = candidate.publicKey
|
||||
profilePictureView.displayName = candidate.displayName
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.update()
|
||||
if (openGroupServer != null && openGroupRoom != null) {
|
||||
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey)
|
||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||
|
@ -67,7 +67,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
||||
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey)
|
||||
// Message detail
|
||||
menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing)
|
||||
menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1
|
||||
// Resend
|
||||
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
|
||||
// Resync
|
||||
|
@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getInt
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
@ -45,7 +46,6 @@ import kotlin.math.roundToInt
|
||||
|
||||
class VisibleMessageContentView : ConstraintLayout {
|
||||
private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) }
|
||||
var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf()
|
||||
var onContentDoubleTap: (() -> Unit)? = null
|
||||
var delegate: VisibleMessageViewDelegate? = null
|
||||
var indexInAdapter: Int = -1
|
||||
@ -59,13 +59,14 @@ class VisibleMessageContentView : ConstraintLayout {
|
||||
// region Updating
|
||||
fun bind(
|
||||
message: MessageRecord,
|
||||
isStartOfMessageCluster: Boolean,
|
||||
isEndOfMessageCluster: Boolean,
|
||||
glide: GlideRequests,
|
||||
isStartOfMessageCluster: Boolean = true,
|
||||
isEndOfMessageCluster: Boolean = true,
|
||||
glide: GlideRequests = GlideApp.with(this),
|
||||
thread: Recipient,
|
||||
searchQuery: String?,
|
||||
contactIsTrusted: Boolean,
|
||||
onAttachmentNeedsDownload: (Long, Long) -> Unit
|
||||
searchQuery: String? = null,
|
||||
contactIsTrusted: Boolean = true,
|
||||
onAttachmentNeedsDownload: (Long, Long) -> Unit,
|
||||
suppressThumbnails: Boolean = false
|
||||
) {
|
||||
// Background
|
||||
val color = if (message.isOutgoing) context.getAccentColor()
|
||||
@ -184,7 +185,7 @@ class VisibleMessageContentView : ConstraintLayout {
|
||||
onContentClick.add { binding.untrustedView.root.showTrustDialog(message.individualRecipient) }
|
||||
}
|
||||
}
|
||||
message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty() -> {
|
||||
message is MmsMessageRecord && !suppressThumbnails && message.slideDeck.asAttachments().isNotEmpty() -> {
|
||||
/*
|
||||
* Images / Video attachment
|
||||
*/
|
||||
@ -237,6 +238,12 @@ class VisibleMessageContentView : ConstraintLayout {
|
||||
binding.contentParent.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
private val onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf()
|
||||
|
||||
fun onContentClick(event: MotionEvent) {
|
||||
onContentClick.forEach { clickHandler -> clickHandler.invoke(event) }
|
||||
}
|
||||
|
||||
private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean =
|
||||
listOf<View>(albumThumbnailView.root, linkPreviewView.root, voiceMessageView.root, quoteView.root).none { it.isVisible }
|
||||
|
||||
|
@ -2,16 +2,17 @@ package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
@ -46,6 +47,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
@ -70,7 +72,6 @@ class VisibleMessageView : LinearLayout {
|
||||
@Inject lateinit var mmsDb: MmsDatabase
|
||||
|
||||
private val binding by lazy { ViewVisibleMessageBinding.bind(this) }
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate()
|
||||
private val swipeToReplyIconRect = Rect()
|
||||
private var dx = 0.0f
|
||||
@ -114,6 +115,7 @@ class VisibleMessageView : LinearLayout {
|
||||
binding.root.disableClipping()
|
||||
binding.mainContainer.disableClipping()
|
||||
binding.messageInnerContainer.disableClipping()
|
||||
binding.messageInnerLayout.disableClipping()
|
||||
binding.messageContentView.root.disableClipping()
|
||||
}
|
||||
// endregion
|
||||
@ -121,14 +123,14 @@ class VisibleMessageView : LinearLayout {
|
||||
// region Updating
|
||||
fun bind(
|
||||
message: MessageRecord,
|
||||
previous: MessageRecord?,
|
||||
next: MessageRecord?,
|
||||
glide: GlideRequests,
|
||||
searchQuery: String?,
|
||||
contact: Contact?,
|
||||
previous: MessageRecord? = null,
|
||||
next: MessageRecord? = null,
|
||||
glide: GlideRequests = GlideApp.with(this),
|
||||
searchQuery: String? = null,
|
||||
contact: Contact? = null,
|
||||
senderSessionID: String,
|
||||
lastSeen: Long,
|
||||
delegate: VisibleMessageViewDelegate?,
|
||||
delegate: VisibleMessageViewDelegate? = null,
|
||||
onAttachmentNeedsDownload: (Long, Long) -> Unit
|
||||
) {
|
||||
val threadID = message.threadId
|
||||
@ -139,7 +141,7 @@ class VisibleMessageView : LinearLayout {
|
||||
// Show profile picture and sender name if this is a group thread AND
|
||||
// the message is incoming
|
||||
binding.moderatorIconImageView.isVisible = false
|
||||
binding.profilePictureView.root.visibility = when {
|
||||
binding.profilePictureView.visibility = when {
|
||||
thread.isGroupRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE
|
||||
thread.isGroupRecipient -> View.INVISIBLE
|
||||
else -> View.GONE
|
||||
@ -148,22 +150,21 @@ class VisibleMessageView : LinearLayout {
|
||||
val bottomMargin = if (isEndOfMessageCluster) resources.getDimensionPixelSize(R.dimen.small_spacing)
|
||||
else ViewUtil.dpToPx(context,2)
|
||||
|
||||
if (binding.profilePictureView.root.visibility == View.GONE) {
|
||||
if (binding.profilePictureView.visibility == View.GONE) {
|
||||
val expirationParams = binding.messageInnerContainer.layoutParams as MarginLayoutParams
|
||||
expirationParams.bottomMargin = bottomMargin
|
||||
binding.messageInnerContainer.layoutParams = expirationParams
|
||||
} else {
|
||||
val avatarLayoutParams = binding.profilePictureView.root.layoutParams as MarginLayoutParams
|
||||
val avatarLayoutParams = binding.profilePictureView.layoutParams as MarginLayoutParams
|
||||
avatarLayoutParams.bottomMargin = bottomMargin
|
||||
binding.profilePictureView.root.layoutParams = avatarLayoutParams
|
||||
binding.profilePictureView.layoutParams = avatarLayoutParams
|
||||
}
|
||||
|
||||
if (isGroupThread && !message.isOutgoing) {
|
||||
if (isEndOfMessageCluster) {
|
||||
binding.profilePictureView.root.publicKey = senderSessionID
|
||||
binding.profilePictureView.root.glide = glide
|
||||
binding.profilePictureView.root.update(message.individualRecipient)
|
||||
binding.profilePictureView.root.setOnClickListener {
|
||||
binding.profilePictureView.publicKey = senderSessionID
|
||||
binding.profilePictureView.update(message.individualRecipient)
|
||||
binding.profilePictureView.setOnClickListener {
|
||||
if (thread.isOpenGroupRecipient) {
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
||||
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) {
|
||||
@ -342,11 +343,14 @@ class VisibleMessageView : LinearLayout {
|
||||
|
||||
private fun updateExpirationTimer(message: MessageRecord) {
|
||||
val container = binding.messageInnerContainer
|
||||
val content = binding.messageContentView.root
|
||||
val expiration = binding.expirationTimerView
|
||||
container.removeAllViewsInLayout()
|
||||
container.addView(if (message.isOutgoing) expiration else content)
|
||||
container.addView(if (message.isOutgoing) content else expiration)
|
||||
val layout = binding.messageInnerLayout
|
||||
|
||||
if (message.isOutgoing) binding.messageContentView.root.bringToFront()
|
||||
else binding.expirationTimerView.bringToFront()
|
||||
|
||||
layout.layoutParams = layout.layoutParams.let { it as FrameLayout.LayoutParams }
|
||||
.apply { gravity = if (message.isOutgoing) Gravity.END else Gravity.START }
|
||||
|
||||
val containerParams = container.layoutParams as ConstraintLayout.LayoutParams
|
||||
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
|
||||
container.layoutParams = containerParams
|
||||
@ -392,7 +396,7 @@ class VisibleMessageView : LinearLayout {
|
||||
val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing)
|
||||
val iconSize = toPx(24, context.resources)
|
||||
val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing
|
||||
val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2)
|
||||
val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.marginBottom - (iconSize / 2)
|
||||
val right = left + iconSize
|
||||
val bottom = top + iconSize
|
||||
swipeToReplyIconRect.left = left
|
||||
@ -412,7 +416,7 @@ class VisibleMessageView : LinearLayout {
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.root.recycle()
|
||||
binding.profilePictureView.recycle()
|
||||
binding.messageContentView.root.recycle()
|
||||
}
|
||||
|
||||
@ -513,7 +517,7 @@ class VisibleMessageView : LinearLayout {
|
||||
}
|
||||
|
||||
fun onContentClick(event: MotionEvent) {
|
||||
binding.messageContentView.root.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) }
|
||||
binding.messageContentView.root.onContentClick(event)
|
||||
}
|
||||
|
||||
private fun onPress(event: MotionEvent) {
|
||||
|
@ -65,7 +65,6 @@ class ConversationView : LinearLayout {
|
||||
} else {
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_view_background)
|
||||
}
|
||||
binding.profilePictureView.root.glide = glide
|
||||
val unreadCount = thread.unreadCount
|
||||
if (thread.recipient.isBlocked) {
|
||||
binding.accentView.setBackgroundResource(R.color.destructive)
|
||||
@ -125,11 +124,11 @@ class ConversationView : LinearLayout {
|
||||
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
||||
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
||||
}
|
||||
binding.profilePictureView.root.update(thread.recipient)
|
||||
binding.profilePictureView.update(thread.recipient)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.root.recycle()
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
|
||||
private fun getUserDisplayName(recipient: Recipient): String? {
|
||||
|
@ -168,8 +168,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
// Set up Glide
|
||||
glide = GlideApp.with(this)
|
||||
// Set up toolbar buttons
|
||||
binding.profileButton.root.glide = glide
|
||||
binding.profileButton.root.setOnClickListener { openSettings() }
|
||||
binding.profileButton.setOnClickListener { openSettings() }
|
||||
binding.searchViewContainer.setOnClickListener {
|
||||
binding.globalSearchInputLayout.requestFocus()
|
||||
}
|
||||
@ -364,8 +363,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true)
|
||||
if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared
|
||||
IdentityKeyUtil.checkUpdate(this)
|
||||
binding.profileButton.root.recycle() // clear cached image before update tje profilePictureView
|
||||
binding.profileButton.root.update()
|
||||
binding.profileButton.recycle() // clear cached image before update tje profilePictureView
|
||||
binding.profileButton.update()
|
||||
if (textSecurePreferences.getHasViewedSeed()) {
|
||||
binding.seedReminderView.isVisible = false
|
||||
}
|
||||
@ -440,10 +439,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
|
||||
private fun updateProfileButton() {
|
||||
binding.profileButton.root.publicKey = publicKey
|
||||
binding.profileButton.root.displayName = textSecurePreferences.getProfileName()
|
||||
binding.profileButton.root.recycle()
|
||||
binding.profileButton.root.update()
|
||||
binding.profileButton.publicKey = publicKey
|
||||
binding.profileButton.displayName = textSecurePreferences.getProfileName()
|
||||
binding.profileButton.recycle()
|
||||
binding.profileButton.update()
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
@ -53,10 +53,9 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
||||
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
|
||||
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
|
||||
with(binding) {
|
||||
profilePictureView.root.publicKey = publicKey
|
||||
profilePictureView.root.glide = GlideApp.with(this@UserDetailsBottomSheet)
|
||||
profilePictureView.root.isLarge = true
|
||||
profilePictureView.root.update(recipient)
|
||||
profilePictureView.publicKey = publicKey
|
||||
profilePictureView.isLarge = true
|
||||
profilePictureView.update(recipient)
|
||||
nameTextViewContainer.visibility = View.VISIBLE
|
||||
nameTextViewContainer.setOnClickListener {
|
||||
if (recipient.isOpenGroupInboxRecipient || recipient.isOpenGroupOutboxRecipient) return@setOnClickListener
|
||||
|
@ -83,22 +83,20 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
||||
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
if (holder is ContentView) {
|
||||
holder.binding.searchResultProfilePicture.root.recycle()
|
||||
holder.binding.searchResultProfilePicture.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
val binding = ViewGlobalSearchResultBinding.bind(view).apply {
|
||||
searchResultProfilePicture.root.glide = GlideApp.with(root)
|
||||
}
|
||||
val binding = ViewGlobalSearchResultBinding.bind(view)
|
||||
|
||||
fun bindPayload(newQuery: String, model: Model) {
|
||||
bindQuery(newQuery, model)
|
||||
}
|
||||
|
||||
fun bind(query: String, model: Model) {
|
||||
binding.searchResultProfilePicture.root.recycle()
|
||||
binding.searchResultProfilePicture.recycle()
|
||||
when (model) {
|
||||
is Model.GroupConversation -> bindModel(query, model)
|
||||
is Model.Contact -> bindModel(query, model)
|
||||
|
@ -87,12 +87,12 @@ private fun getHighlight(query: String?, toSearch: String): Spannable? {
|
||||
}
|
||||
|
||||
fun ContentView.bindModel(query: String?, model: GroupConversation) {
|
||||
binding.searchResultProfilePicture.root.isVisible = true
|
||||
binding.searchResultProfilePicture.isVisible = true
|
||||
binding.searchResultSavedMessages.isVisible = false
|
||||
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
|
||||
binding.searchResultTimestamp.isVisible = false
|
||||
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
|
||||
binding.searchResultProfilePicture.root.update(threadRecipient)
|
||||
binding.searchResultProfilePicture.update(threadRecipient)
|
||||
val nameString = model.groupRecord.title
|
||||
binding.searchResultTitle.text = getHighlight(query, nameString)
|
||||
|
||||
@ -108,14 +108,14 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
|
||||
}
|
||||
|
||||
fun ContentView.bindModel(query: String?, model: ContactModel) {
|
||||
binding.searchResultProfilePicture.root.isVisible = true
|
||||
binding.searchResultProfilePicture.isVisible = true
|
||||
binding.searchResultSavedMessages.isVisible = false
|
||||
binding.searchResultSubtitle.isVisible = false
|
||||
binding.searchResultTimestamp.isVisible = false
|
||||
binding.searchResultSubtitle.text = null
|
||||
val recipient =
|
||||
Recipient.from(binding.root.context, Address.fromSerialized(model.contact.sessionID), false)
|
||||
binding.searchResultProfilePicture.root.update(recipient)
|
||||
binding.searchResultProfilePicture.update(recipient)
|
||||
val nameString = model.contact.getSearchName()
|
||||
binding.searchResultTitle.text = getHighlight(query, nameString)
|
||||
}
|
||||
@ -124,12 +124,12 @@ fun ContentView.bindModel(model: SavedMessages) {
|
||||
binding.searchResultSubtitle.isVisible = false
|
||||
binding.searchResultTimestamp.isVisible = false
|
||||
binding.searchResultTitle.setText(R.string.note_to_self)
|
||||
binding.searchResultProfilePicture.root.isVisible = false
|
||||
binding.searchResultProfilePicture.isVisible = false
|
||||
binding.searchResultSavedMessages.isVisible = true
|
||||
}
|
||||
|
||||
fun ContentView.bindModel(query: String?, model: Message) {
|
||||
binding.searchResultProfilePicture.root.isVisible = true
|
||||
binding.searchResultProfilePicture.isVisible = true
|
||||
binding.searchResultSavedMessages.isVisible = false
|
||||
binding.searchResultTimestamp.isVisible = true
|
||||
// val hasUnreads = model.unread > 0
|
||||
@ -138,7 +138,7 @@ fun ContentView.bindModel(query: String?, model: Message) {
|
||||
// binding.unreadCountTextView.text = model.unread.toString()
|
||||
// }
|
||||
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||
binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient)
|
||||
binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient)
|
||||
val textSpannable = SpannableStringBuilder()
|
||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||
// group chat, bind
|
||||
|
@ -34,7 +34,6 @@ class MessageRequestView : LinearLayout {
|
||||
// region Updating
|
||||
fun bind(thread: ThreadRecord, glide: GlideRequests) {
|
||||
this.thread = thread
|
||||
binding.profilePictureView.root.glide = glide
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||
?: thread.recipient.address.toString()
|
||||
binding.displayNameTextView.text = senderDisplayName
|
||||
@ -44,12 +43,12 @@ class MessageRequestView : LinearLayout {
|
||||
binding.snippetTextView.text = snippet
|
||||
|
||||
post {
|
||||
binding.profilePictureView.root.update(thread.recipient)
|
||||
binding.profilePictureView.update(thread.recipient)
|
||||
}
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.profilePictureView.root.recycle()
|
||||
binding.profilePictureView.recycle()
|
||||
}
|
||||
|
||||
private fun getUserDisplayName(recipient: Recipient): String? {
|
||||
|
@ -38,7 +38,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
holder.binding.profilePictureView.root.recycle()
|
||||
holder.binding.profilePictureView.recycle()
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
@ -48,8 +48,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap
|
||||
|
||||
fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
|
||||
binding.recipientName.text = selectable.item.name
|
||||
with (binding.profilePictureView.root) {
|
||||
glide = this@ViewHolder.glide
|
||||
with (binding.profilePictureView) {
|
||||
update(selectable.item)
|
||||
}
|
||||
binding.root.setOnClickListener { toggle(selectable) }
|
||||
|
@ -88,10 +88,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
val displayName = getDisplayName()
|
||||
glide = GlideApp.with(this)
|
||||
with(binding) {
|
||||
setupProfilePictureView(profilePictureView.root)
|
||||
profilePictureView.root.setOnClickListener {
|
||||
showEditProfilePictureUI()
|
||||
}
|
||||
setupProfilePictureView(profilePictureView)
|
||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
||||
btnGroupNameDisplay.text = displayName
|
||||
publicKeyTextView.text = hexEncodedPublicKey
|
||||
@ -116,7 +114,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
||||
|
||||
private fun setupProfilePictureView(view: ProfilePictureView) {
|
||||
view.glide = glide
|
||||
view.apply {
|
||||
publicKey = hexEncodedPublicKey
|
||||
displayName = getDisplayName()
|
||||
@ -255,8 +252,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
binding.btnGroupNameDisplay.text = displayName
|
||||
}
|
||||
if (isUpdatingProfilePicture) {
|
||||
binding.profilePictureView.root.recycle() // Clear the cached image before updating
|
||||
binding.profilePictureView.root.update()
|
||||
binding.profilePictureView.recycle() // Clear the cached image before updating
|
||||
binding.profilePictureView.update()
|
||||
}
|
||||
binding.loader.isVisible = false
|
||||
}
|
||||
|
@ -144,7 +144,6 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
super(itemView);
|
||||
this.callback = callback;
|
||||
avatar = itemView.findViewById(R.id.reactions_bottom_view_avatar);
|
||||
avatar.glide = GlideApp.with(itemView);
|
||||
recipient = itemView.findViewById(R.id.reactions_bottom_view_recipient_name);
|
||||
remove = itemView.findViewById(R.id.reactions_bottom_view_recipient_remove);
|
||||
}
|
||||
|
63
app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt
Normal file
63
app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt
Normal file
@ -0,0 +1,63 @@
|
||||
package org.thoughtcrime.securesms.ui
|
||||
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val colorDestructive = Color(0xffFF453A)
|
||||
|
||||
const val classicDark0 = 0xff111111
|
||||
const val classicDark1 = 0xff1B1B1B
|
||||
const val classicDark2 = 0xff2D2D2D
|
||||
const val classicDark3 = 0xff414141
|
||||
const val classicDark4 = 0xff767676
|
||||
const val classicDark5 = 0xffA1A2A1
|
||||
const val classicDark6 = 0xffFFFFFF
|
||||
|
||||
const val classicLight0 = 0xff000000
|
||||
const val classicLight1 = 0xff6D6D6D
|
||||
const val classicLight2 = 0xffA1A2A1
|
||||
const val classicLight3 = 0xffDFDFDF
|
||||
const val classicLight4 = 0xffF0F0F0
|
||||
const val classicLight5 = 0xffF9F9F9
|
||||
const val classicLight6 = 0xffFFFFFF
|
||||
|
||||
const val oceanDark0 = 0xff000000
|
||||
const val oceanDark1 = 0xff1A1C28
|
||||
const val oceanDark2 = 0xff252735
|
||||
const val oceanDark3 = 0xff2B2D40
|
||||
const val oceanDark4 = 0xff3D4A5D
|
||||
const val oceanDark5 = 0xffA6A9CE
|
||||
const val oceanDark6 = 0xff5CAACC
|
||||
const val oceanDark7 = 0xffFFFFFF
|
||||
|
||||
const val oceanLight0 = 0xff000000
|
||||
const val oceanLight1 = 0xff19345D
|
||||
const val oceanLight2 = 0xff6A6E90
|
||||
const val oceanLight3 = 0xff5CAACC
|
||||
const val oceanLight4 = 0xffB3EDF2
|
||||
const val oceanLight5 = 0xffE7F3F4
|
||||
const val oceanLight6 = 0xffECFAFB
|
||||
const val oceanLight7 = 0xffFCFFFF
|
||||
|
||||
val ocean_accent = Color(0xff57C9FA)
|
||||
|
||||
val oceanLights = arrayOf(oceanLight0, oceanLight1, oceanLight2, oceanLight3, oceanLight4, oceanLight5, oceanLight6, oceanLight7)
|
||||
val oceanDarks = arrayOf(oceanDark0, oceanDark1, oceanDark2, oceanDark3, oceanDark4, oceanDark5, oceanDark6, oceanDark7)
|
||||
val classicLights = arrayOf(classicLight0, classicLight1, classicLight2, classicLight3, classicLight4, classicLight5, classicLight6)
|
||||
val classicDarks = arrayOf(classicDark0, classicDark1, classicDark2, classicDark3, classicDark4, classicDark5, classicDark6)
|
||||
|
||||
val oceanLightColors = oceanLights.map(::Color)
|
||||
val oceanDarkColors = oceanDarks.map(::Color)
|
||||
val classicLightColors = classicLights.map(::Color)
|
||||
val classicDarkColors = classicDarks.map(::Color)
|
||||
|
||||
val blackAlpha40 = Color.Black.copy(alpha = 0.4f)
|
||||
|
||||
@Composable
|
||||
fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)
|
||||
|
||||
@Composable
|
||||
fun destructiveButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = colorDestructive)
|
182
app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
Normal file
182
app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
Normal file
@ -0,0 +1,182 @@
|
||||
package org.thoughtcrime.securesms.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.google.accompanist.pager.HorizontalPagerIndicator
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
|
||||
@Composable
|
||||
fun ItemButton(
|
||||
text: String,
|
||||
@DrawableRes icon: Int,
|
||||
colors: ButtonColors = transparentButtonColors(),
|
||||
contentDescription: String = text,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp),
|
||||
colors = colors,
|
||||
onClick = onClick,
|
||||
shape = RectangleShape,
|
||||
) {
|
||||
Box(modifier = Modifier
|
||||
.width(80.dp)
|
||||
.fillMaxHeight()) {
|
||||
Icon(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
Text(text, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Cell(content: @Composable () -> Unit) {
|
||||
CellWithPaddingAndMargin(padding = 0.dp) { content() }
|
||||
}
|
||||
@Composable
|
||||
fun CellNoMargin(content: @Composable () -> Unit) {
|
||||
CellWithPaddingAndMargin(padding = 0.dp, margin = 0.dp) { content() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CellWithPaddingAndMargin(
|
||||
padding: Dp = 24.dp,
|
||||
margin: Dp = 32.dp,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Card(
|
||||
backgroundColor = MaterialTheme.colors.cellColor,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
elevation = 0.dp,
|
||||
modifier = Modifier
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = margin),
|
||||
) {
|
||||
Box(Modifier.padding(padding)) { content() }
|
||||
}
|
||||
}
|
||||
|
||||
private val Colors.cellColor: Color
|
||||
@Composable
|
||||
get() = LocalExtraColors.current.settingsBackground
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) {
|
||||
if (pagerState.pageCount >= 2) Card(
|
||||
shape = RoundedCornerShape(50.dp),
|
||||
backgroundColor = Color.Black.copy(alpha = 0.4f),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.padding(8.dp)) {
|
||||
HorizontalPagerIndicator(
|
||||
pagerState = pagerState,
|
||||
pageCount = pagerState.pageCount,
|
||||
activeColor = Color.White,
|
||||
inactiveColor = classicDarkColors[5])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RowScope.CarouselPrevButton(pagerState: PagerState) {
|
||||
CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_prev, -1)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RowScope.CarouselNextButton(pagerState: PagerState) {
|
||||
CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_next, 1)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RowScope.CarouselButton(
|
||||
pagerState: PagerState,
|
||||
enabled: Boolean,
|
||||
@DrawableRes id: Int,
|
||||
delta: Int
|
||||
) {
|
||||
if (pagerState.pageCount <= 1) Spacer(modifier = Modifier.width(32.dp))
|
||||
else {
|
||||
val animationScope = rememberCoroutineScope()
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.align(Alignment.CenterVertically),
|
||||
enabled = enabled,
|
||||
onClick = { animationScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + delta) } }) {
|
||||
Icon(
|
||||
painter = painterResource(id = id),
|
||||
contentDescription = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Divider() {
|
||||
androidx.compose.material.Divider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.Avatar(recipient: Recipient) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = {
|
||||
ProfilePictureView(it).apply { update(recipient) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.width(46.dp)
|
||||
.height(46.dp)
|
||||
)
|
||||
}
|
||||
}
|
34
app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt
Normal file
34
app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Compatibility class to allow ViewModels to use strings and string resources interchangeably.
|
||||
*/
|
||||
sealed class GetString {
|
||||
@Composable
|
||||
abstract fun string(): String
|
||||
data class FromString(val string: String): GetString() {
|
||||
@Composable
|
||||
override fun string(): String = string
|
||||
}
|
||||
data class FromResId(@StringRes val resId: Int): GetString() {
|
||||
@Composable
|
||||
override fun string(): String = stringResource(resId)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun GetString(@StringRes resId: Int) = GetString.FromResId(resId)
|
||||
fun GetString(string: String) = GetString.FromString(string)
|
||||
|
||||
|
||||
/**
|
||||
* Represents some text with an associated title.
|
||||
*/
|
||||
data class TitledText(val title: GetString, val text: String) {
|
||||
constructor(title: String, text: String): this(GetString(title), text)
|
||||
constructor(@StringRes title: Int, text: String): this(GetString(title), text)
|
||||
}
|
76
app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt
Normal file
76
app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt
Normal file
@ -0,0 +1,76 @@
|
||||
package org.thoughtcrime.securesms.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import network.loki.messenger.R
|
||||
|
||||
val LocalExtraColors = staticCompositionLocalOf<ExtraColors> { error("No Custom Attribute value provided") }
|
||||
|
||||
|
||||
data class ExtraColors(
|
||||
val settingsBackground: Color,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts current Theme to Compose Theme.
|
||||
*/
|
||||
@Composable
|
||||
fun AppTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val extraColors = LocalContext.current.run {
|
||||
ExtraColors(
|
||||
settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground),
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalExtraColors provides extraColors) {
|
||||
AppCompatTheme {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color =
|
||||
MaterialColors.getColor(this, attr, defaultValue).let(::Color)
|
||||
|
||||
/**
|
||||
* Set the theme and a background for Compose Previews.
|
||||
*/
|
||||
@Composable
|
||||
fun PreviewTheme(
|
||||
themeResId: Int,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContext provides ContextThemeWrapper(LocalContext.current, themeResId)
|
||||
) {
|
||||
AppTheme {
|
||||
Box(modifier = Modifier.background(color = MaterialTheme.colors.background)) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeResPreviewParameterProvider : PreviewParameterProvider<Int> {
|
||||
override val values = sequenceOf(
|
||||
R.style.Classic_Dark,
|
||||
R.style.Classic_Light,
|
||||
R.style.Ocean_Dark,
|
||||
R.style.Ocean_Light,
|
||||
)
|
||||
}
|
5
app/src/main/res/drawable/ic_expand.xml
Normal file
5
app/src/main/res/drawable/ic_expand.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="27dp"
|
||||
android:viewportHeight="27" android:viewportWidth="26"
|
||||
android:width="26dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ffffff" android:pathData="M7.38,12.265C7.792,12.265 8.093,11.962 8.093,11.55V11.079L7.957,9.008L9.494,10.629L11.394,12.542C11.528,12.682 11.703,12.746 11.893,12.746C12.336,12.746 12.654,12.448 12.654,12.009C12.654,11.807 12.58,11.627 12.441,11.489L10.533,9.588L8.911,8.052L10.995,8.188H11.497C11.909,8.188 12.217,7.892 12.217,7.476C12.217,7.058 11.915,6.758 11.497,6.758H7.849C7.097,6.758 6.662,7.193 6.662,7.944V11.55C6.662,11.957 6.969,12.265 7.38,12.265ZM14.497,19.444H18.146C18.897,19.444 19.338,19.009 19.338,18.257V14.65C19.338,14.245 19.031,13.937 18.614,13.937C18.208,13.937 17.901,14.24 17.901,14.65V15.123L18.043,17.193L16.5,15.572L14.605,13.66C14.472,13.52 14.291,13.456 14.101,13.456C13.664,13.456 13.34,13.754 13.34,14.191C13.34,14.394 13.42,14.574 13.559,14.712L15.461,16.613L17.089,18.149L15.005,18.013H14.497C14.086,18.013 13.777,18.309 13.777,18.726C13.777,19.144 14.086,19.444 14.497,19.444Z"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="21dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="21">
|
||||
<path
|
||||
android:pathData="M13.468,20.291C15.669,20.291 17.795,19.548 19.307,18.33C20.22,17.627 20.414,16.646 19.821,15.943C19.207,15.229 18.34,15.224 17.526,15.794C16.296,16.745 15.074,17.235 13.468,17.235C10.109,17.235 7.327,15 6.532,12.011H8.261C9.138,12.011 9.378,11.134 8.868,10.451L5.96,6.434C5.449,5.74 4.581,5.695 4.055,6.434L1.184,10.451C0.674,11.15 0.899,12.011 1.776,12.011H3.556C4.435,16.889 8.431,20.291 13.468,20.291ZM13.438,0.291C11.255,0.291 9.111,1.019 7.617,2.238C6.7,2.94 6.509,3.921 7.102,4.624C7.717,5.338 8.584,5.34 9.38,4.773C10.612,3.837 11.835,3.332 13.438,3.332C16.8,3.332 19.579,5.567 20.392,8.556H18.57C17.678,8.556 17.45,9.432 17.948,10.116L20.871,14.133C21.382,14.827 22.249,14.872 22.775,14.133L25.647,10.116C26.156,9.432 25.932,8.556 25.04,8.556H23.35C22.485,3.675 18.492,0.291 13.438,0.291Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_message_details__reply.xml
Normal file
9
app/src/main/res/drawable/ic_message_details__reply.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
<path
|
||||
android:pathData="M10.847,3.572V7.974C20.432,8.707 23.521,16.919 23.868,20.933C19.76,14.869 13.476,14.412 10.847,14.942V19.466L2.962,11.702L10.847,3.572Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_message_details__trash.xml
Normal file
9
app/src/main/res/drawable/ic_message_details__trash.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
<path
|
||||
android:pathData="M8.749,24.43H19.167C21.186,24.43 22.073,23.433 22.385,21.427L23.972,5.825L22.071,5.907L20.492,21.315C20.35,22.226 19.89,22.591 19.076,22.591H8.847C8.017,22.591 7.566,22.226 7.432,21.315L5.853,5.907L3.952,5.825L5.539,21.427C5.843,23.441 6.738,24.43 8.749,24.43ZM4.063,6.85H23.863C25.195,6.85 25.962,5.998 25.962,4.677V3.244C25.962,1.924 25.195,1.072 23.863,1.072H4.063C2.782,1.072 1.962,1.924 1.962,3.244V4.677C1.962,5.998 2.732,6.85 4.063,6.85ZM4.44,5.102C3.99,5.102 3.794,4.898 3.794,4.446V3.474C3.794,3.023 3.99,2.819 4.44,2.819H23.492C23.942,2.819 24.13,3.023 24.13,3.474V4.446C24.13,4.898 23.942,5.102 23.492,5.102H4.44Z"
|
||||
android:fillColor="#FF3A3A"/>
|
||||
</vector>
|
8
app/src/main/res/drawable/ic_next.xml
Normal file
8
app/src/main/res/drawable/ic_next.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<vector android:autoMirrored="true" android:height="17dp"
|
||||
android:viewportHeight="17" android:viewportWidth="13"
|
||||
android:width="13dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M13,16.004l-13,-0l-0,-16l13,-0z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M0.646,1.736L10.112,7.933L0.444,14.268C0.323,14.343 0.222,14.438 0.144,14.547C0.067,14.657 0.015,14.779 -0.007,14.906C-0.029,15.033 -0.022,15.163 0.014,15.287C0.05,15.412 0.115,15.529 0.203,15.632C0.292,15.734 0.404,15.82 0.532,15.885C0.66,15.95 0.801,15.991 0.948,16.008C1.095,16.024 1.244,16.015 1.386,15.981C1.529,15.946 1.662,15.887 1.778,15.808L12.353,8.88C12.466,8.805 12.562,8.711 12.635,8.605C12.687,8.563 12.734,8.518 12.778,8.47C12.955,8.266 13.031,8.009 12.99,7.756C12.949,7.503 12.794,7.274 12.559,7.12L1.984,0.193C1.868,0.117 1.736,0.061 1.595,0.029C1.454,-0.003 1.307,-0.011 1.163,0.006C1.018,0.024 0.88,0.066 0.754,0.13C0.628,0.194 0.519,0.279 0.431,0.381C0.343,0.482 0.278,0.597 0.241,0.72C0.204,0.843 0.195,0.971 0.215,1.097C0.235,1.223 0.284,1.344 0.358,1.454C0.432,1.563 0.53,1.659 0.646,1.736Z"/>
|
||||
</group>
|
||||
</vector>
|
8
app/src/main/res/drawable/ic_prev.xml
Normal file
8
app/src/main/res/drawable/ic_prev.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<vector android:autoMirrored="true" android:height="17dp"
|
||||
android:viewportHeight="17" android:viewportWidth="12"
|
||||
android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0.004h12v16h-12z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M11.403,14.272L2.666,8.075L11.59,1.74C11.701,1.665 11.795,1.57 11.867,1.46C11.938,1.351 11.986,1.229 12.006,1.102C12.027,0.975 12.02,0.845 11.987,0.721C11.954,0.596 11.894,0.479 11.812,0.376C11.73,0.274 11.627,0.187 11.509,0.123C11.391,0.058 11.26,0.016 11.125,0C10.989,-0.016 10.852,-0.007 10.72,0.027C10.589,0.062 10.466,0.12 10.359,0.2L0.597,7.127C0.493,7.203 0.405,7.297 0.337,7.403C0.289,7.444 0.245,7.49 0.205,7.538C0.042,7.742 -0.029,7.999 0.009,8.252C0.047,8.505 0.19,8.734 0.407,8.887L10.168,15.815C10.275,15.891 10.398,15.947 10.528,15.979C10.658,16.011 10.793,16.019 10.927,16.001C11.06,15.984 11.188,15.942 11.304,15.878C11.42,15.814 11.521,15.728 11.602,15.627C11.684,15.526 11.743,15.411 11.777,15.288C11.811,15.165 11.82,15.037 11.801,14.911C11.783,14.785 11.738,14.664 11.67,14.554C11.601,14.445 11.511,14.349 11.403,14.272Z"/>
|
||||
</group>
|
||||
</vector>
|
@ -27,9 +27,8 @@
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp">
|
||||
|
||||
<include
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profileButton"
|
||||
layout="@layout/view_profile_picture"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
android:layout_height="@dimen/small_profile_picture_size"
|
||||
android:layout_alignParentLeft="true"
|
||||
|
@ -21,7 +21,7 @@
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/large_profile_picture_size"
|
||||
android:layout_height="@dimen/large_profile_picture_size"
|
||||
|
@ -9,7 +9,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:background="?selectableItemBackground"
|
||||
android:id="@+id/backgroundContainer">
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_height="@dimen/small_profile_picture_size"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
|
@ -45,7 +45,7 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:layout_margin="30dp"
|
||||
android:id="@+id/profile_picture_view"
|
||||
android:layout_gravity="center"
|
||||
|
@ -13,7 +13,7 @@
|
||||
app:behavior_hideable="true"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/large_profile_picture_size"
|
||||
android:layout_height="@dimen/large_profile_picture_size"
|
||||
|
@ -13,7 +13,7 @@
|
||||
app:behavior_hideable="true"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/large_profile_picture_size"
|
||||
android:layout_height="@dimen/large_profile_picture_size"
|
||||
|
@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/reactions_bottom_view_avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -15,7 +15,7 @@
|
||||
android:paddingHorizontal="@dimen/large_spacing"
|
||||
android:paddingVertical="@dimen/small_spacing">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
android:layout_height="@dimen/small_profile_picture_size" />
|
||||
|
@ -14,7 +14,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorAccent" />
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/medium_profile_picture_size"
|
||||
android:layout_height="@dimen/medium_profile_picture_size"
|
||||
|
@ -18,7 +18,7 @@
|
||||
android:id="@+id/search_result_profile_picture_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/search_result_profile_picture"
|
||||
android:layout_width="@dimen/medium_profile_picture_size"
|
||||
|
@ -13,7 +13,7 @@
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="32dp">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/very_small_profile_picture_size"
|
||||
android:layout_height="@dimen/very_small_profile_picture_size"
|
||||
|
@ -17,7 +17,7 @@
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="32dp">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/very_small_profile_picture_size"
|
||||
android:layout_height="@dimen/very_small_profile_picture_size"
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/medium_profile_picture_size"
|
||||
android:layout_height="@dimen/medium_profile_picture_size"
|
||||
|
@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:contentDescription="@string/AccessibilityId_profile_picture"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/doubleModeImageViewContainer"
|
||||
@ -44,4 +40,4 @@
|
||||
android:layout_height="@dimen/large_profile_picture_size"
|
||||
android:background="@drawable/profile_picture_view_large_background" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.ProfilePictureView>
|
||||
</merge>
|
@ -15,7 +15,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="@dimen/medium_spacing">
|
||||
|
||||
<include layout="@layout/view_profile_picture"
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
android:layout_height="@dimen/small_profile_picture_size"
|
||||
|
@ -67,9 +67,8 @@
|
||||
android:gravity="bottom"
|
||||
android:paddingBottom="@dimen/small_spacing">
|
||||
|
||||
<include
|
||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
android:id="@+id/profilePictureView"
|
||||
layout="@layout/view_profile_picture"
|
||||
android:layout_marginBottom="@dimen/small_spacing"
|
||||
android:layout_marginEnd="@dimen/small_spacing"
|
||||
android:layout_width="@dimen/very_small_profile_picture_size"
|
||||
@ -107,35 +106,40 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/messageInnerContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/profilePictureView"
|
||||
app:layout_constraintStart_toEndOf="@+id/profilePictureView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/senderNameTextView">
|
||||
|
||||
<include layout="@layout/view_visible_message_content"
|
||||
android:id="@+id/messageContentView"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/messageInnerLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||
android:id="@+id/expirationTimerView"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginHorizontal="@dimen/small_spacing"
|
||||
android:contentDescription="@string/AccessibilityId_timer_icon"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible"
|
||||
tools:src="@drawable/timer60"
|
||||
tools:tint="@color/black"/>
|
||||
<include layout="@layout/view_visible_message_content"
|
||||
android:id="@+id/messageContentView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||
android:id="@+id/expirationTimerView"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginHorizontal="@dimen/small_spacing"
|
||||
android:contentDescription="@string/AccessibilityId_timer_icon"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible"
|
||||
tools:src="@drawable/timer60"
|
||||
tools:tint="@color/black"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/view_emoji_reactions"
|
||||
android:id="@+id/emojiReactionsView"
|
||||
|
@ -4,11 +4,15 @@
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="resend">Resend</string>
|
||||
<string name="reply">Reply</string>
|
||||
<string name="ban">Ban</string>
|
||||
<string name="please_wait">Please wait…</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="image">Image</string>
|
||||
<string name="note_to_self">Note to Self</string>
|
||||
<string name="version_s">Version %s</string>
|
||||
<string name="expand">Expand</string>
|
||||
<!--Accessibility ID's-->
|
||||
<!-- Landing Page -->
|
||||
<string name="AccessibilityId_create_session_id">Create session ID</string>
|
||||
@ -526,6 +530,12 @@
|
||||
<string name="message_details_header__to">To:</string>
|
||||
<string name="message_details_header__from">From:</string>
|
||||
<string name="message_details_header__with">With:</string>
|
||||
<string name="message_details_header__file_id">File Id:</string>
|
||||
<string name="message_details_header__file_type">File Type:</string>
|
||||
<string name="message_details_header__file_size">File Size:</string>
|
||||
<string name="message_details_header__resolution">Resolution:</string>
|
||||
<string name="message_details_header__duration">Duration:</string>
|
||||
|
||||
<!-- AndroidManifest.xml -->
|
||||
<string name="AndroidManifest__create_passphrase">Create passphrase</string>
|
||||
<string name="AndroidManifest__select_contacts">Select contacts</string>
|
||||
|
@ -342,6 +342,8 @@
|
||||
<item name="prominentButtonColor">?colorAccent</item>
|
||||
<item name="elementBorderColor">@color/classic_dark_3</item>
|
||||
|
||||
<item name="isLightTheme">false</item>
|
||||
|
||||
<!-- Home screen -->
|
||||
<item name="searchBackgroundColor">#1B1B1B</item>
|
||||
<item name="searchIconColor">#E5E5E8</item>
|
||||
@ -424,6 +426,7 @@
|
||||
<item name="android:colorBackgroundFloating">?colorPrimary</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="O_MR1">true</item>
|
||||
<item name="isLightTheme">true</item>
|
||||
<item name="android:isLightTheme" tools:targetApi="Q">true</item>
|
||||
<item name="android:statusBarColor">?colorPrimary</item>
|
||||
|
||||
@ -487,7 +490,6 @@
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:textColorHint">@color/ocean_dark_5</item>
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:colorBackground">@color/default_background_start</item>
|
||||
<item name="android:navigationBarColor">@color/navigation_bar</item>
|
||||
<item name="default_background_end">?colorPrimary</item>
|
||||
<item name="default_background_start">?colorPrimaryDark</item>
|
||||
@ -507,6 +509,8 @@
|
||||
<item name="prominentButtonColor">?colorAccent</item>
|
||||
<item name="elementBorderColor">@color/ocean_dark_4</item>
|
||||
|
||||
<item name="isLightTheme">false</item>
|
||||
|
||||
<!-- Home screen -->
|
||||
<item name="searchBackgroundColor">@color/ocean_dark_3</item>
|
||||
<item name="searchIconColor">@color/ocean_dark_7</item>
|
||||
@ -570,7 +574,6 @@
|
||||
<item name="android:textColorHint">@color/ocean_light_6</item>
|
||||
<item name="android:navigationBarColor">@color/ocean_light_navigation_bar</item>
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:colorBackground">@color/default_background_start</item>
|
||||
<item name="default_background_end">@color/ocean_light_7</item>
|
||||
<item name="default_background_start">@color/ocean_light_6</item>
|
||||
<item name="colorCellBackground">@color/ocean_light_5</item>
|
||||
@ -594,6 +597,7 @@
|
||||
<item name="android:colorBackgroundFloating">?colorPrimary</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="O_MR1">true</item>
|
||||
<item name="isLightTheme">true</item>
|
||||
<item name="android:isLightTheme" tools:targetApi="Q">true</item>
|
||||
<item name="android:statusBarColor">?colorPrimary</item>
|
||||
|
||||
|
@ -22,7 +22,7 @@ fun <T> LiveData<T>.getOrAwaitValue(
|
||||
var data: T? = null
|
||||
val latch = CountDownLatch(1)
|
||||
val observer = object : Observer<T> {
|
||||
override fun onChanged(o: T?) {
|
||||
override fun onChanged(o: T) {
|
||||
data = o
|
||||
latch.countDown()
|
||||
this@getOrAwaitValue.removeObserver(this)
|
||||
|
@ -8,9 +8,14 @@ buildscript {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.google.gms:google-services:$googleServicesVersion"
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
classpath "com.squareup:javapoet:1.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
plugins{
|
||||
id("com.google.dagger.hilt.android") version "2.44" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
@ -12,26 +12,28 @@
|
||||
# org.gradle.parallel=true
|
||||
#Mon Jun 26 09:56:43 AEST 2023
|
||||
android.enableJetifier=true
|
||||
|
||||
gradlePluginVersion=7.3.1
|
||||
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.unsafe.configuration-cache=true
|
||||
|
||||
googleServicesVersion=4.3.12
|
||||
kotlinVersion=1.8.21
|
||||
android.useAndroidX=true
|
||||
appcompatVersion=1.6.1
|
||||
coreVersion=1.8.0
|
||||
coroutinesVersion=1.6.4
|
||||
curve25519Version=0.6.0
|
||||
daggerVersion=2.40.1
|
||||
daggerVersion=2.46.1
|
||||
glideVersion=4.11.0
|
||||
googleServicesVersion=4.3.12
|
||||
gradlePluginVersion=7.3.1
|
||||
jacksonDatabindVersion=2.9.8
|
||||
junitVersion=4.13.2
|
||||
kotlinVersion=1.6.21
|
||||
kotlinxJsonVersion=1.3.3
|
||||
kovenantVersion=3.3.0
|
||||
lifecycleVersion=2.5.1
|
||||
materialVersion=1.8.0
|
||||
mockitoKotlinVersion=4.1.0
|
||||
okhttpVersion=3.12.1
|
||||
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.unsafe.configuration-cache=true
|
||||
pagingVersion=3.0.0
|
||||
preferenceVersion=1.2.0
|
||||
protobufVersion=2.5.0
|
||||
|
@ -286,10 +286,9 @@ class BatchMessageReceiveJob(
|
||||
val openGroupID = data.getStringOrDefault(OPEN_GROUP_ID_KEY, null)
|
||||
|
||||
val parameters = (0 until numMessages).map { index ->
|
||||
val data = contents[index]
|
||||
val serverHash = serverHashes[index].let { if (it.isEmpty()) null else it }
|
||||
val serverId = openGroupMessageServerIDs[index].let { if (it == -1L) null else it }
|
||||
MessageReceiveParameters(data, serverHash, serverId)
|
||||
MessageReceiveParameters(contents[index], serverHash, serverId)
|
||||
}
|
||||
|
||||
return BatchMessageReceiveJob(parameters, openGroupID)
|
||||
|
@ -2,6 +2,7 @@ package org.session.libsession.messaging.mentions
|
||||
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import java.util.*
|
||||
|
||||
object MentionsManager {
|
||||
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
||||
@ -32,9 +33,9 @@ object MentionsManager {
|
||||
candidates.sortedBy { it.displayName }
|
||||
if (query.length >= 2) {
|
||||
// Filter out any non-matching candidates
|
||||
candidates = candidates.filter { it.displayName.toLowerCase().contains(query.toLowerCase()) }
|
||||
candidates = candidates.filter { it.displayName.lowercase(Locale.getDefault()).contains(query.lowercase(Locale.getDefault())) }
|
||||
// Sort based on where in the candidate the query occurs
|
||||
candidates.sortedBy { it.displayName.toLowerCase().indexOf(query.toLowerCase()) }
|
||||
candidates.sortedBy { it.displayName.lowercase(Locale.getDefault()).indexOf(query.lowercase(Locale.getDefault())) }
|
||||
}
|
||||
// Return
|
||||
return candidates
|
||||
|
@ -4,6 +4,10 @@ import android.content.Context
|
||||
import org.session.libsession.R
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.calls.CallMessageType
|
||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED
|
||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING
|
||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED
|
||||
import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
@ -13,46 +17,41 @@ import org.session.libsession.utilities.truncateIdForDisplay
|
||||
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
|
||||
|
||||
object UpdateMessageBuilder {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
|
||||
fun getSenderName(senderId: String) = storage.getContactWithSessionID(senderId)
|
||||
?.displayName(Contact.ContactContext.REGULAR)
|
||||
?: truncateIdForDisplay(senderId)
|
||||
|
||||
fun buildGroupUpdateMessage(context: Context, updateMessageData: UpdateMessageData, senderId: String? = null, isOutgoing: Boolean = false): String {
|
||||
var message = ""
|
||||
val updateData = updateMessageData.kind ?: return message
|
||||
if (!isOutgoing && senderId == null) return message
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderName: String = if (!isOutgoing) {
|
||||
storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId)
|
||||
} else { context.getString(R.string.MessageRecord_you) }
|
||||
val updateData = updateMessageData.kind
|
||||
if (updateData == null || !isOutgoing && senderId == null) return ""
|
||||
val senderName: String = if (isOutgoing) context.getString(R.string.MessageRecord_you)
|
||||
else getSenderName(senderId!!)
|
||||
|
||||
when (updateData) {
|
||||
is UpdateMessageData.Kind.GroupCreation -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_created_a_new_group)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
|
||||
}
|
||||
return when (updateData) {
|
||||
is UpdateMessageData.Kind.GroupCreation -> if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_created_a_new_group)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupNameChange -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupNameChange -> if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberAdded -> {
|
||||
val members = updateData.updatedMembers.joinToString(", ") {
|
||||
storage.getContactWithSessionID(it)?.displayName(Contact.ContactContext.REGULAR) ?: it
|
||||
}
|
||||
message = if (isOutgoing) {
|
||||
val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName)
|
||||
if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_added_s_to_the_group, members)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members)
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberRemoved -> {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
// 1st case: you are part of the removed members
|
||||
message = if (userPublicKey in updateData.updatedMembers) {
|
||||
return if (userPublicKey in updateData.updatedMembers) {
|
||||
if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_left_group)
|
||||
} else {
|
||||
@ -60,9 +59,7 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
} else {
|
||||
// 2nd case: you are not part of the removed members
|
||||
val members = updateData.updatedMembers.joinToString(", ") {
|
||||
storage.getContactWithSessionID(it)?.displayName(Contact.ContactContext.REGULAR) ?: it
|
||||
}
|
||||
val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName)
|
||||
if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members)
|
||||
} else {
|
||||
@ -70,23 +67,19 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberLeft -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_left_group)
|
||||
} else {
|
||||
context.getString(R.string.ConversationItem_group_action_left, senderName)
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberLeft -> if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_left_group)
|
||||
} else {
|
||||
context.getString(R.string.ConversationItem_group_action_left, senderName)
|
||||
}
|
||||
is UpdateMessageData.Kind.OpenGroupInvitation -> { /*Handled externally*/ }
|
||||
else -> return ""
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
fun buildExpirationTimerMessage(context: Context, duration: Long, senderId: String? = null, isOutgoing: Boolean = false): String {
|
||||
if (!isOutgoing && senderId == null) return ""
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderName: String? = if (!isOutgoing) {
|
||||
storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId)
|
||||
val senderName: String = if (!isOutgoing) {
|
||||
getSenderName(senderId!!)
|
||||
} else { context.getString(R.string.MessageRecord_you) }
|
||||
return if (duration <= 0) {
|
||||
if (isOutgoing) {
|
||||
@ -128,8 +121,7 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
|
||||
fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null): String {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderName = storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId)
|
||||
val senderName = getSenderName(senderId!!)
|
||||
return when (kind) {
|
||||
DataExtractionNotificationInfoMessage.Kind.SCREENSHOT ->
|
||||
context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName)
|
||||
@ -138,18 +130,12 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderName = storage.getContactWithSessionID(sender)?.displayName(Contact.ContactContext.REGULAR) ?: sender
|
||||
return when (type) {
|
||||
CallMessageType.CALL_MISSED ->
|
||||
context.getString(R.string.MessageRecord_missed_call_from, senderName)
|
||||
CallMessageType.CALL_INCOMING ->
|
||||
context.getString(R.string.MessageRecord_s_called_you, senderName)
|
||||
CallMessageType.CALL_OUTGOING ->
|
||||
context.getString(R.string.MessageRecord_called_s, senderName)
|
||||
CallMessageType.CALL_FIRST_MISSED ->
|
||||
context.getString(R.string.MessageRecord_missed_call_from, senderName)
|
||||
fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String =
|
||||
when (type) {
|
||||
CALL_INCOMING -> R.string.MessageRecord_s_called_you
|
||||
CALL_OUTGOING -> R.string.MessageRecord_called_s
|
||||
CALL_MISSED, CALL_FIRST_MISSED -> R.string.MessageRecord_missed_call_from
|
||||
}.let {
|
||||
context.getString(it, storage.getContactWithSessionID(sender)?.displayName(Contact.ContactContext.REGULAR) ?: sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user