mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-26 22:20:20 +00:00
Add an internal recipient details screen.
This commit is contained in:
@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.util.visible
|
||||
class DSLSettingsAdapter : MappingAdapter() {
|
||||
init {
|
||||
registerFactory(ClickPreference::class.java, LayoutFactory(::ClickPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(LongClickPreference::class.java, LayoutFactory(::LongClickPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(TextPreference::class.java, LayoutFactory(::TextPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(RadioListPreference::class.java, LayoutFactory(::RadioListPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(MultiSelectListPreference::class.java, LayoutFactory(::MultiSelectListPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
@@ -82,6 +83,16 @@ class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ClickPref
|
||||
}
|
||||
}
|
||||
|
||||
class LongClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<LongClickPreference>(itemView) {
|
||||
override fun bind(model: LongClickPreference) {
|
||||
super.bind(model)
|
||||
itemView.setOnLongClickListener() {
|
||||
model.onLongClick()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RadioListPreferenceViewHolder(itemView: View) : PreferenceViewHolder<RadioListPreference>(itemView) {
|
||||
override fun bind(model: RadioListPreference) {
|
||||
super.bind(model)
|
||||
|
||||
@@ -302,11 +302,9 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
customPref(
|
||||
InternalPreference.Model(
|
||||
recipient = state.recipient,
|
||||
onDisableProfileSharingClick = {
|
||||
viewModel.disableProfileSharing()
|
||||
},
|
||||
onDeleteSessionClick = {
|
||||
viewModel.deleteSession()
|
||||
onInternalDetailsClicked = {
|
||||
val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToInternalDetailsSettingsFragment(state.recipient.id)
|
||||
navController.navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.libsignal.util.guava.Optional
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -204,29 +203,6 @@ class ConversationSettingsRepository(
|
||||
}
|
||||
}
|
||||
|
||||
fun disableProfileSharingForInternalUser(recipientId: RecipientId) {
|
||||
Preconditions.checkArgument(FeatureFlags.internalUser(), "Internal users only!")
|
||||
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipientId, false)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteSessionForInternalUser(recipientId: RecipientId) {
|
||||
Preconditions.checkArgument(FeatureFlags.internalUser(), "Internal users only!")
|
||||
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
|
||||
if (recipient.hasUuid()) {
|
||||
DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipient.requireUuid().toString())
|
||||
}
|
||||
if (recipient.hasE164()) {
|
||||
DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipient.requireE164())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun isMessageRequestAccepted(recipient: Recipient): Boolean {
|
||||
return RecipientUtil.isMessageRequestAccepted(context, recipient)
|
||||
|
||||
@@ -114,10 +114,6 @@ sealed class ConversationSettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
open fun disableProfileSharing(): Unit = error("This ViewModel does not support this interaction")
|
||||
|
||||
open fun deleteSession(): Unit = error("This ViewModel does not support this interaction")
|
||||
|
||||
open fun initiateGroupUpgrade(): Unit = error("This ViewModel does not support this interaction")
|
||||
|
||||
private class RecipientSettingsViewModel(
|
||||
@@ -237,14 +233,6 @@ sealed class ConversationSettingsViewModel(
|
||||
override fun unblock() {
|
||||
repository.unblock(recipientId)
|
||||
}
|
||||
|
||||
override fun disableProfileSharing() {
|
||||
repository.disableProfileSharingForInternalUser(recipientId)
|
||||
}
|
||||
|
||||
override fun deleteSession() {
|
||||
repository.deleteSessionForInternalUser(recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
private class GroupSettingsViewModel(
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation
|
||||
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.Hex
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import java.util.Objects
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Shows internal details about a recipient that you can view from the conversation settings.
|
||||
*/
|
||||
class InternalConversationSettingsFragment : DSLSettingsFragment(
|
||||
titleId = R.string.ConversationSettingsFragment__internal_details
|
||||
) {
|
||||
|
||||
private val viewModel: InternalViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
val recipientId = InternalConversationSettingsFragmentArgs.fromBundle(requireArguments()).recipientId
|
||||
MyViewModelFactory(recipientId)
|
||||
}
|
||||
)
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: InternalState): DSLConfiguration {
|
||||
val recipient = state.recipient
|
||||
return configure {
|
||||
sectionHeaderPref(DSLSettingsText.from("Data"))
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("RecipientId"),
|
||||
summary = DSLSettingsText.from(recipient.id.serialize())
|
||||
)
|
||||
|
||||
val uuid = recipient.uuid.transform(UUID::toString).or("null")
|
||||
longClickPref(
|
||||
title = DSLSettingsText.from("UUID"),
|
||||
summary = DSLSettingsText.from(uuid),
|
||||
onLongClick = { copyToClipboard(uuid) }
|
||||
)
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Profile Name"),
|
||||
summary = DSLSettingsText.from("[${recipient.profileName.givenName}] [${state.recipient.profileName.familyName}]")
|
||||
)
|
||||
|
||||
val profileKeyBase64 = recipient.profileKey?.let(Base64::encodeBytes) ?: "None"
|
||||
longClickPref(
|
||||
title = DSLSettingsText.from("Profile Key (Base64)"),
|
||||
summary = DSLSettingsText.from(profileKeyBase64),
|
||||
onLongClick = { copyToClipboard(profileKeyBase64) }
|
||||
)
|
||||
|
||||
val profileKeyHex = recipient.profileKey?.let(Hex::toStringCondensed) ?: ""
|
||||
longClickPref(
|
||||
title = DSLSettingsText.from("Profile Key (Hex)"),
|
||||
summary = DSLSettingsText.from(profileKeyHex),
|
||||
onLongClick = { copyToClipboard(profileKeyHex) }
|
||||
)
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Sealed Sender Mode"),
|
||||
summary = DSLSettingsText.from(recipient.unidentifiedAccessMode.toString())
|
||||
)
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Profile Sharing (AKA \"Whitelisted\")"),
|
||||
summary = DSLSettingsText.from(recipient.isProfileSharing.toString())
|
||||
)
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Capabilities"),
|
||||
summary = DSLSettingsText.from(buildCapabilitySpan(recipient))
|
||||
)
|
||||
|
||||
sectionHeaderPref(DSLSettingsText.from("Actions"))
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Disable Profile Sharing"),
|
||||
summary = DSLSettingsText.from("Clears profile sharing/whitelisted status, which should cause the Message Request UI to show."),
|
||||
onClick = {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle("Are you sure?")
|
||||
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> DatabaseFactory.getRecipientDatabase(requireContext()).setProfileSharing(recipient.id, false) }
|
||||
.show()
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Delete Session"),
|
||||
summary = DSLSettingsText.from("Deletes the session, essentially guaranteeing an encryption error if they send you a message."),
|
||||
onClick = {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle("Are you sure?")
|
||||
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (recipient.hasUuid()) {
|
||||
DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipient.requireUuid().toString())
|
||||
}
|
||||
if (recipient.hasE164()) {
|
||||
DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipient.requireE164())
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
Util.copyToClipboard(requireContext(), text)
|
||||
Toast.makeText(requireContext(), "Copied to clipboard", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun buildCapabilitySpan(recipient: Recipient): CharSequence {
|
||||
return TextUtils.concat(
|
||||
colorize("GV2", recipient.groupsV2Capability),
|
||||
", ",
|
||||
colorize("GV1Migration", recipient.groupsV1MigrationCapability),
|
||||
", ",
|
||||
colorize("AnnouncementGroup", recipient.announcementGroupCapability),
|
||||
", ",
|
||||
colorize("SenderKey", recipient.senderKeyCapability),
|
||||
", ",
|
||||
colorize("ChangeNumber", recipient.changeNumberCapability),
|
||||
)
|
||||
}
|
||||
|
||||
private fun colorize(name: String, support: Recipient.Capability): CharSequence {
|
||||
return when (support) {
|
||||
Recipient.Capability.SUPPORTED -> SpanUtil.color(Color.rgb(0, 150, 0), name)
|
||||
Recipient.Capability.NOT_SUPPORTED -> SpanUtil.color(Color.RED, name)
|
||||
Recipient.Capability.UNKNOWN -> SpanUtil.italic(name)
|
||||
}
|
||||
}
|
||||
|
||||
class InternalViewModel(
|
||||
val recipientId: RecipientId
|
||||
) : ViewModel(), RecipientForeverObserver {
|
||||
|
||||
private val store = Store(InternalState(Recipient.resolved(recipientId)))
|
||||
|
||||
val state = store.stateLiveData
|
||||
val liveRecipient = Recipient.live(recipientId)
|
||||
|
||||
init {
|
||||
liveRecipient.observeForever(this)
|
||||
}
|
||||
|
||||
override fun onRecipientChanged(recipient: Recipient) {
|
||||
store.update { InternalState(recipient) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
liveRecipient.removeForeverObserver(this)
|
||||
}
|
||||
}
|
||||
|
||||
class MyViewModelFactory(val recipientId: RecipientId) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return Objects.requireNonNull(modelClass.cast(InternalViewModel(recipientId)))
|
||||
}
|
||||
}
|
||||
|
||||
data class InternalState(
|
||||
val recipient: Recipient
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.preferences
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -19,37 +18,9 @@ object InternalPreference {
|
||||
|
||||
class Model(
|
||||
private val recipient: Recipient,
|
||||
val onDisableProfileSharingClick: () -> Unit,
|
||||
val onDeleteSessionClick: () -> Unit
|
||||
val onInternalDetailsClicked: () -> Unit,
|
||||
) : PreferenceModel<Model>() {
|
||||
|
||||
val body: String get() {
|
||||
return String.format(
|
||||
"""
|
||||
-- Profile Name --
|
||||
[${recipient.profileName.givenName}] [${recipient.profileName.familyName}]
|
||||
|
||||
-- Profile Sharing --
|
||||
${recipient.isProfileSharing}
|
||||
|
||||
-- Profile Key (Base64) --
|
||||
${recipient.profileKey?.let(Base64::encodeBytes) ?: "None"}
|
||||
|
||||
-- Profile Key (Hex) --
|
||||
${recipient.profileKey?.let(Hex::toStringCondensed) ?: "None"}
|
||||
|
||||
-- Sealed Sender Mode --
|
||||
${recipient.unidentifiedAccessMode}
|
||||
|
||||
-- UUID --
|
||||
${recipient.uuid.transform { obj: UUID -> obj.toString() }.or("None")}
|
||||
|
||||
-- RecipientId --
|
||||
${recipient.id.serialize()}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
return recipient == newItem.recipient
|
||||
}
|
||||
@@ -57,14 +28,10 @@ object InternalPreference {
|
||||
|
||||
private class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val body: TextView = itemView.findViewById(R.id.internal_preference_body)
|
||||
private val disableProfileSharing: View = itemView.findViewById(R.id.internal_disable_profile_sharing)
|
||||
private val deleteSession: View = itemView.findViewById(R.id.internal_delete_session)
|
||||
private val internalDetails: View = itemView.findViewById(R.id.internal_details)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
body.text = model.body
|
||||
disableProfileSharing.setOnClickListener { model.onDisableProfileSharingClick() }
|
||||
deleteSession.setOnClickListener { model.onDeleteSessionClick() }
|
||||
internalDetails.setOnClickListener { model.onInternalDetailsClicked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,17 @@ class DSLConfiguration {
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun longClickPref(
|
||||
title: DSLSettingsText,
|
||||
summary: DSLSettingsText? = null,
|
||||
icon: DSLSettingsIcon? = null,
|
||||
isEnabled: Boolean = true,
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
val preference = LongClickPreference(title, summary, icon, isEnabled, onLongClick)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun externalLinkPref(
|
||||
title: DSLSettingsText,
|
||||
icon: DSLSettingsIcon? = null,
|
||||
@@ -216,6 +227,14 @@ class ClickPreference(
|
||||
val onClick: () -> Unit
|
||||
) : PreferenceModel<ClickPreference>()
|
||||
|
||||
class LongClickPreference(
|
||||
override val title: DSLSettingsText,
|
||||
override val summary: DSLSettingsText? = null,
|
||||
override val icon: DSLSettingsIcon? = null,
|
||||
override val isEnabled: Boolean = true,
|
||||
val onLongClick: () -> Unit
|
||||
) : PreferenceModel<LongClickPreference>()
|
||||
|
||||
class ExternalLinkPreference(
|
||||
override val title: DSLSettingsText,
|
||||
override val icon: DSLSettingsIcon?,
|
||||
|
||||
@@ -74,10 +74,10 @@ public final class InternalValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show detailed recipient info in the {@link org.thoughtcrime.securesms.recipients.ui.managerecipient.ManageRecipientFragment}.
|
||||
* Show detailed recipient info in the {@link org.thoughtcrime.securesms.components.settings.conversation.InternalConversationSettingsFragment}.
|
||||
*/
|
||||
public synchronized boolean recipientDetails() {
|
||||
return FeatureFlags.internalUser() && getBoolean(RECIPIENT_DETAILS, false);
|
||||
return FeatureFlags.internalUser() && getBoolean(RECIPIENT_DETAILS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,35 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/internal_preference_body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body2"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/internal_disable_profile_sharing"
|
||||
android:id="@+id/internal_details"
|
||||
style="@style/Signal.Widget.Button.Small.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/preferences__internal_disable_profile_sharing" />
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/preferences__internal_details" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/internal_delete_session"
|
||||
style="@style/Signal.Widget.Button.Small.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/preferences__internal_delete_session" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -75,6 +75,20 @@
|
||||
app:nullable="true" />
|
||||
</action>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_conversationSettingsFragment_to_internalDetailsSettingsFragment"
|
||||
app:destination="@id/internalDetailsSettingsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
app:argType="org.thoughtcrime.securesms.recipients.RecipientId" />
|
||||
|
||||
</action>
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
@@ -95,6 +109,16 @@
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/internalDetailsSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.InternalConversationSettingsFragment">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
app:argType="org.thoughtcrime.securesms.recipients.RecipientId" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/permissionsSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.permissions.PermissionsSettingsFragment">
|
||||
|
||||
@@ -2517,6 +2517,7 @@
|
||||
<string name="preferences__internal_current_version_d_at_density_s" translatable="false">Current version: %1$d at density %2$s</string>
|
||||
<string name="preferences__internal_delete_all_dynamic_shortcuts" translatable="false">Delete all dynamic shortcuts</string>
|
||||
<string name="preferences__internal_click_to_delete_all_dynamic_shortcuts" translatable="false">Click to delete all dynamic shortcuts</string>
|
||||
<string name="preferences__internal_details" translatable="false">Internal Details</string>
|
||||
<string name="preferences__internal_disable_profile_sharing" translatable="false">Disable Profile Sharing</string>
|
||||
<string name="preferences__internal_delete_session" translatable="false">Delete Session</string>
|
||||
<string name="preferences__internal_sender_key" translatable="false">Sender Key</string>
|
||||
@@ -3687,6 +3688,7 @@
|
||||
<string name="ConversationSettingsFragment__search">Search</string>
|
||||
<string name="ConversationSettingsFragment__disappearing_messages">Disappearing messages</string>
|
||||
<string name="ConversationSettingsFragment__sounds_and_notifications">Sounds & notifications</string>
|
||||
<string name="ConversationSettingsFragment__internal_details" translatable="false">Internal details</string>
|
||||
<string name="ConversationSettingsFragment__contact_details">Contact details</string>
|
||||
<string name="ConversationSettingsFragment__view_safety_number">View safety number</string>
|
||||
<string name="ConversationSettingsFragment__block">Block</string>
|
||||
|
||||
Reference in New Issue
Block a user