mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-03 23:15:44 +01:00
Display member label on recipient details sheet.
This commit is contained in:
committed by
Greyson Parrelli
parent
fae4ca91bd
commit
d5b2f4fdd3
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.groups.memberlabel
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
|
||||
/**
|
||||
* Displays member label text with an optional emoji.
|
||||
*/
|
||||
@Composable
|
||||
fun MemberLabelPill(
|
||||
emoji: String?,
|
||||
text: String,
|
||||
tintColor: Color,
|
||||
modifier: Modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
||||
textStyle: TextStyle = MaterialTheme.typography.bodyLarge
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
val backgroundColor = tintColor.copy(alpha = if (isDark) 0.32f else 0.10f)
|
||||
|
||||
val textColor = if (isDark) {
|
||||
Color.White.copy(alpha = 0.25f).compositeOver(tintColor)
|
||||
} else {
|
||||
Color.Black.copy(alpha = 0.30f).compositeOver(tintColor)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = backgroundColor,
|
||||
shape = RoundedCornerShape(percent = 50)
|
||||
)
|
||||
.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (!emoji.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = emoji,
|
||||
style = textStyle,
|
||||
modifier = Modifier.padding(end = 5.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (text.isNotEmpty()) {
|
||||
Text(
|
||||
text = text,
|
||||
color = textColor,
|
||||
style = textStyle,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MemberLabelWithEmojiPreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
MemberLabelPill(
|
||||
emoji = "🧠",
|
||||
text = "Zero-Knowledge Know-It-All",
|
||||
tintColor = Color(0xFF7A3DF5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MemberLabelTextOnlyPreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
MemberLabelPill(
|
||||
emoji = null,
|
||||
text = "Zero-Knowledge Know-It-All",
|
||||
tintColor = Color(0xFF7A3DF5)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.groups.memberlabel
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
|
||||
/**
|
||||
* @see MemberLabelPill
|
||||
*/
|
||||
class MemberLabelPillView : AbstractComposeView {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
private var memberLabel: MemberLabel? by mutableStateOf(null)
|
||||
private var tintColor: Color by mutableStateOf(Color.Unspecified)
|
||||
|
||||
fun setLabel(label: MemberLabel, tintColor: Color) {
|
||||
this.memberLabel = label
|
||||
this.tintColor = tintColor
|
||||
}
|
||||
|
||||
fun setLabel(label: MemberLabel, @ColorInt tintColor: Int) {
|
||||
this.memberLabel = label
|
||||
this.tintColor = Color(tintColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
memberLabel?.let { label ->
|
||||
MemberLabelPill(
|
||||
emoji = label.emoji,
|
||||
text = label.text,
|
||||
tintColor = tintColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
package org.thoughtcrime.securesms.groups.memberlabel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.util.orNull
|
||||
@@ -22,26 +24,45 @@ import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
/**
|
||||
* Handles the retrieval and modification of group member labels.
|
||||
*/
|
||||
class MemberLabelRepository(
|
||||
private val groupId: GroupId.V2,
|
||||
class MemberLabelRepository private constructor(
|
||||
private val context: Context = AppDependencies.application,
|
||||
private val groupsTable: GroupTable = SignalDatabase.groups
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val instance: MemberLabelRepository by lazy { MemberLabelRepository() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the member label for a specific recipient in the group.
|
||||
*/
|
||||
suspend fun getLabel(recipientId: RecipientId): MemberLabel? = withContext(Dispatchers.IO) {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
suspend fun getLabel(groupId: GroupId.V2, recipientId: RecipientId): MemberLabel? = withContext(Dispatchers.IO) {
|
||||
getLabel(groupId, Recipient.resolved(recipientId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the member label for a specific recipient in the group (blocking version for Java compatibility).
|
||||
*/
|
||||
@WorkerThread
|
||||
fun getLabelJava(groupId: GroupId.V2, recipient: Recipient): MemberLabel? = runBlocking { getLabel(groupId, recipient) }
|
||||
|
||||
/**
|
||||
* Gets the member label for a specific recipient in the group.
|
||||
*/
|
||||
suspend fun getLabel(groupId: GroupId.V2, recipient: Recipient): MemberLabel? = withContext(Dispatchers.IO) {
|
||||
if (!RemoteConfig.receiveMemberLabels) {
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
val aci = recipient.serviceId.orNull() as? ServiceId.ACI ?: return@withContext null
|
||||
val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return@withContext null
|
||||
|
||||
return@withContext groupRecord.requireV2GroupProperties().memberLabel(aci)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group member label for the current user.
|
||||
*/
|
||||
suspend fun setLabel(label: MemberLabel): Unit = withContext(Dispatchers.IO) {
|
||||
suspend fun setLabel(groupId: GroupId.V2, label: MemberLabel): Unit = withContext(Dispatchers.IO) {
|
||||
if (!RemoteConfig.sendMemberLabels) {
|
||||
throw IllegalStateException("Set member label not allowed due to remote config.")
|
||||
}
|
||||
|
||||
@@ -21,18 +21,11 @@ private const val MIN_LABEL_TEXT_LENGTH = 1
|
||||
private const val MAX_LABEL_TEXT_LENGTH = 24
|
||||
|
||||
class MemberLabelViewModel(
|
||||
private val memberLabelRepo: MemberLabelRepository,
|
||||
private val memberLabelRepo: MemberLabelRepository = MemberLabelRepository.instance,
|
||||
private val groupId: GroupId.V2,
|
||||
private val recipientId: RecipientId
|
||||
) : ViewModel() {
|
||||
|
||||
constructor(
|
||||
groupId: GroupId.V2,
|
||||
recipientId: RecipientId
|
||||
) : this(
|
||||
memberLabelRepo = MemberLabelRepository(groupId = groupId),
|
||||
recipientId = recipientId
|
||||
)
|
||||
|
||||
private var originalLabelEmoji: String = ""
|
||||
private var originalLabelText: String = ""
|
||||
|
||||
@@ -45,7 +38,7 @@ class MemberLabelViewModel(
|
||||
|
||||
private fun loadExistingLabel() {
|
||||
viewModelScope.launch(SignalDispatchers.IO) {
|
||||
val memberLabel = memberLabelRepo.getLabel(recipientId)
|
||||
val memberLabel = memberLabelRepo.getLabel(groupId, recipientId)
|
||||
originalLabelEmoji = memberLabel?.emoji.orEmpty()
|
||||
originalLabelText = memberLabel?.text.orEmpty()
|
||||
|
||||
@@ -103,6 +96,7 @@ class MemberLabelViewModel(
|
||||
|
||||
val currentState = internalUiState.value
|
||||
memberLabelRepo.setLabel(
|
||||
groupId = groupId,
|
||||
label = MemberLabel(
|
||||
emoji = currentState.labelEmoji.ifEmpty { null },
|
||||
text = currentState.labelText
|
||||
|
||||
Reference in New Issue
Block a user