mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 20:15:21 +00:00
Merge branch 'refactor' of https://github.com/oxen-io/session-android into multi_device
This commit is contained in:
commit
b2884c84e9
@ -23,6 +23,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -79,10 +81,11 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
|||||||
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
|
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final int MAX_CACHE_SIZE = 40;
|
private static final int MAX_CACHE_SIZE = 1000;
|
||||||
private static final String TAG = ConversationAdapter.class.getSimpleName();
|
private static final String TAG = ConversationAdapter.class.getSimpleName();
|
||||||
private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
|
private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
|
||||||
Collections.synchronizedMap(new LRUCache<String, SoftReference<MessageRecord>>(MAX_CACHE_SIZE));
|
Collections.synchronizedMap(new LRUCache<String, SoftReference<MessageRecord>>(MAX_CACHE_SIZE));
|
||||||
|
private final SparseArray<String> positionToCacheRef = new SparseArray<>();
|
||||||
|
|
||||||
private static final int MESSAGE_TYPE_OUTGOING = 0;
|
private static final int MESSAGE_TYPE_OUTGOING = 0;
|
||||||
private static final int MESSAGE_TYPE_INCOMING = 1;
|
private static final int MESSAGE_TYPE_INCOMING = 1;
|
||||||
@ -191,6 +194,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
|||||||
@Override
|
@Override
|
||||||
public void changeCursor(Cursor cursor) {
|
public void changeCursor(Cursor cursor) {
|
||||||
messageRecordCache.clear();
|
messageRecordCache.clear();
|
||||||
|
positionToCacheRef.clear();
|
||||||
super.cleanFastRecords();
|
super.cleanFastRecords();
|
||||||
super.changeCursor(cursor);
|
super.changeCursor(cursor);
|
||||||
}
|
}
|
||||||
@ -198,8 +202,39 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
|||||||
@Override
|
@Override
|
||||||
protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) {
|
protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) {
|
||||||
int adapterPosition = viewHolder.getAdapterPosition();
|
int adapterPosition = viewHolder.getAdapterPosition();
|
||||||
MessageRecord previousRecord = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getRecordForPositionOrThrow(adapterPosition + 1) : null;
|
|
||||||
MessageRecord nextRecord = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getRecordForPositionOrThrow(adapterPosition - 1) : null;
|
String prevCachedId = positionToCacheRef.get(adapterPosition + 1,null);
|
||||||
|
String nextCachedId = positionToCacheRef.get(adapterPosition - 1, null);
|
||||||
|
|
||||||
|
MessageRecord previousRecord = null;
|
||||||
|
if (adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1)) {
|
||||||
|
if (prevCachedId != null && messageRecordCache.containsKey(prevCachedId)) {
|
||||||
|
SoftReference<MessageRecord> prevSoftRecord = messageRecordCache.get(prevCachedId);
|
||||||
|
MessageRecord prevCachedRecord = prevSoftRecord.get();
|
||||||
|
if (prevCachedRecord != null) {
|
||||||
|
previousRecord = prevCachedRecord;
|
||||||
|
} else {
|
||||||
|
previousRecord = getRecordForPositionOrThrow(adapterPosition + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previousRecord = getRecordForPositionOrThrow(adapterPosition + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageRecord nextRecord = null;
|
||||||
|
if (adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1)) {
|
||||||
|
if (nextCachedId != null && messageRecordCache.containsKey(nextCachedId)) {
|
||||||
|
SoftReference<MessageRecord> nextSoftRecord = messageRecordCache.get(nextCachedId);
|
||||||
|
MessageRecord nextCachedRecord = nextSoftRecord.get();
|
||||||
|
if (nextCachedRecord != null) {
|
||||||
|
nextRecord = nextCachedRecord;
|
||||||
|
} else {
|
||||||
|
nextRecord = getRecordForPositionOrThrow(adapterPosition - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextRecord = getRecordForPositionOrThrow(adapterPosition - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewHolder.getView().bind(messageRecord,
|
viewHolder.getView().bind(messageRecord,
|
||||||
Optional.fromNullable(previousRecord),
|
Optional.fromNullable(previousRecord),
|
||||||
|
@ -337,6 +337,9 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
recipient.removeListener(this);
|
recipient.removeListener(this);
|
||||||
}
|
}
|
||||||
|
if (profilePictureView != null) {
|
||||||
|
profilePictureView.recycle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageRecord getMessageRecord() {
|
public MessageRecord getMessageRecord() {
|
||||||
|
@ -130,6 +130,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
// Set up recycler view
|
// Set up recycler view
|
||||||
val cursor = DatabaseFactory.getThreadDatabase(this).conversationList
|
val cursor = DatabaseFactory.getThreadDatabase(this).conversationList
|
||||||
val homeAdapter = HomeAdapter(this, cursor)
|
val homeAdapter = HomeAdapter(this, cursor)
|
||||||
|
homeAdapter.setHasStableIds(true)
|
||||||
homeAdapter.glide = glide
|
homeAdapter.glide = glide
|
||||||
homeAdapter.conversationClickListener = this
|
homeAdapter.conversationClickListener = this
|
||||||
recyclerView.adapter = homeAdapter
|
recyclerView.adapter = homeAdapter
|
||||||
|
@ -35,6 +35,11 @@ class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter<
|
|||||||
viewHolder.view.bind(thread, isTyping, glide)
|
viewHolder.view.bind(thread, isTyping, glide)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onItemViewRecycled(holder: ViewHolder?) {
|
||||||
|
super.onItemViewRecycled(holder)
|
||||||
|
holder?.view?.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getThread(cursor: Cursor): ThreadRecord? {
|
private fun getThread(cursor: Cursor): ThreadRecord? {
|
||||||
return threadDatabase.readerFor(cursor).current
|
return threadDatabase.readerFor(cursor).current
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,8 @@ class ConversationView : LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpViewHierarchy() {
|
private fun setUpViewHierarchy() {
|
||||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
LayoutInflater.from(context)
|
||||||
val contentView = inflater.inflate(R.layout.view_conversation, null)
|
.inflate(R.layout.view_conversation, this)
|
||||||
addView(contentView)
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -84,6 +83,10 @@ class ConversationView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun recycle() {
|
||||||
|
profilePictureView.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getUserDisplayName(publicKey: String?): String? {
|
private fun getUserDisplayName(publicKey: String?): String? {
|
||||||
if (TextUtils.isEmpty(publicKey)) return null
|
if (TextUtils.isEmpty(publicKey)) return null
|
||||||
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
|
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
|
||||||
|
@ -29,6 +29,7 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
var additionalDisplayName: String? = null
|
var additionalDisplayName: String? = null
|
||||||
var isRSSFeed = false
|
var isRSSFeed = false
|
||||||
var isLarge = false
|
var isLarge = false
|
||||||
|
private val imagesCached = mutableSetOf<String>()
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context) : super(context) {
|
constructor(context: Context) : super(context) {
|
||||||
@ -104,54 +105,78 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
fun update() {
|
fun update() {
|
||||||
val publicKey = publicKey ?: return
|
val publicKey = publicKey ?: return
|
||||||
val additionalPublicKey = additionalPublicKey
|
val additionalPublicKey = additionalPublicKey
|
||||||
doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) {
|
||||||
singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
|
setProfilePictureIfNeeded(
|
||||||
largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
|
doubleModeImageView1,
|
||||||
|
publicKey,
|
||||||
|
displayName,
|
||||||
|
R.dimen.small_profile_picture_size)
|
||||||
|
setProfilePictureIfNeeded(
|
||||||
|
doubleModeImageView2,
|
||||||
|
additionalPublicKey,
|
||||||
|
additionalDisplayName,
|
||||||
|
R.dimen.small_profile_picture_size)
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
glide.clear(doubleModeImageView1)
|
||||||
|
glide.clear(doubleModeImageView2)
|
||||||
|
View.INVISIBLE
|
||||||
|
}
|
||||||
|
singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) {
|
||||||
|
setProfilePictureIfNeeded(
|
||||||
|
singleModeImageView,
|
||||||
|
publicKey,
|
||||||
|
displayName,
|
||||||
|
R.dimen.medium_profile_picture_size)
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
glide.clear(singleModeImageView)
|
||||||
|
View.INVISIBLE
|
||||||
|
}
|
||||||
|
largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) {
|
||||||
|
setProfilePictureIfNeeded(
|
||||||
|
largeSingleModeImageView,
|
||||||
|
publicKey,
|
||||||
|
displayName,
|
||||||
|
R.dimen.large_profile_picture_size)
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
glide.clear(largeSingleModeImageView)
|
||||||
|
View.INVISIBLE
|
||||||
|
}
|
||||||
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
setProfilePictureIfNeeded(
|
|
||||||
doubleModeImageView1,
|
|
||||||
publicKey,
|
|
||||||
displayName,
|
|
||||||
R.dimen.small_profile_picture_size)
|
|
||||||
setProfilePictureIfNeeded(
|
|
||||||
doubleModeImageView2,
|
|
||||||
additionalPublicKey ?: "",
|
|
||||||
additionalDisplayName,
|
|
||||||
R.dimen.small_profile_picture_size)
|
|
||||||
setProfilePictureIfNeeded(
|
|
||||||
singleModeImageView,
|
|
||||||
publicKey,
|
|
||||||
displayName,
|
|
||||||
R.dimen.medium_profile_picture_size)
|
|
||||||
setProfilePictureIfNeeded(
|
|
||||||
largeSingleModeImageView,
|
|
||||||
publicKey,
|
|
||||||
displayName,
|
|
||||||
R.dimen.large_profile_picture_size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
|
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
|
||||||
glide.clear(imageView)
|
|
||||||
if (publicKey.isNotEmpty()) {
|
if (publicKey.isNotEmpty()) {
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false);
|
if (imagesCached.contains(publicKey)) return
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||||
val signalProfilePicture = recipient.contactPhoto
|
val signalProfilePicture = recipient.contactPhoto
|
||||||
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
|
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
|
||||||
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
|
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
|
||||||
|
glide.clear(imageView)
|
||||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||||
|
imagesCached.add(publicKey)
|
||||||
} else {
|
} else {
|
||||||
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
||||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
val hepk = if (recipient.isLocalNumber && masterPublicKey != null) masterPublicKey else publicKey
|
val hepk = if (recipient.isLocalNumber && masterPublicKey != null) masterPublicKey else publicKey
|
||||||
|
glide.clear(imageView)
|
||||||
glide.load(AvatarPlaceholderGenerator.generate(
|
glide.load(AvatarPlaceholderGenerator.generate(
|
||||||
context,
|
context,
|
||||||
sizeInPX,
|
sizeInPX,
|
||||||
hepk,
|
hepk,
|
||||||
displayName
|
displayName
|
||||||
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||||
|
imagesCached.add(publicKey)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imageView.setImageDrawable(null)
|
imageView.setImageDrawable(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun recycle() {
|
||||||
|
imagesCached.clear()
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -190,7 +190,7 @@
|
|||||||
android:textSize="@dimen/medium_font_size"
|
android:textSize="@dimen/medium_font_size"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="Invite" />
|
android:text="@string/activity_settings_invite_button_title" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/seedButtonTopSeparator"
|
android:id="@+id/seedButtonTopSeparator"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<string name="yes">Oui</string>
|
<string name="yes">Oui</string>
|
||||||
<string name="no">Non</string>
|
<string name="no">Non</string>
|
||||||
<string name="delete">Supprimer</string>
|
<string name="delete">Supprimer</string>
|
||||||
|
<string name="ban">Bannir</string>
|
||||||
<string name="please_wait">Veuillez patienter…</string>
|
<string name="please_wait">Veuillez patienter…</string>
|
||||||
<string name="save">Enregistrer</string>
|
<string name="save">Enregistrer</string>
|
||||||
<string name="note_to_self">Note à mon intention</string>
|
<string name="note_to_self">Note à mon intention</string>
|
||||||
@ -176,6 +177,7 @@
|
|||||||
<item quantity="one">Le message sélectionné sera irrémédiablement supprimé.</item>
|
<item quantity="one">Le message sélectionné sera irrémédiablement supprimé.</item>
|
||||||
<item quantity="other">Les %1$d messages sélectionnés seront irrémédiablement supprimés</item>
|
<item quantity="other">Les %1$d messages sélectionnés seront irrémédiablement supprimés</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="ConversationFragment_ban_selected_user">Bannir cet utilisateur?</string>
|
||||||
<string name="ConversationFragment_save_to_sd_card">Enregistrer dans la mémoire ?</string>
|
<string name="ConversationFragment_save_to_sd_card">Enregistrer dans la mémoire ?</string>
|
||||||
<plurals name="ConversationFragment_saving_n_media_to_storage_warning">
|
<plurals name="ConversationFragment_saving_n_media_to_storage_warning">
|
||||||
<item quantity="one">La sauvegarde du média dans l’espace de stockage autorisera d’autres applications à y accéder.\n\nContinuer ?</item>
|
<item quantity="one">La sauvegarde du média dans l’espace de stockage autorisera d’autres applications à y accéder.\n\nContinuer ?</item>
|
||||||
@ -200,6 +202,8 @@
|
|||||||
<string name="ConversationFragment_sms">Textos</string>
|
<string name="ConversationFragment_sms">Textos</string>
|
||||||
<string name="ConversationFragment_deleting">Suppression</string>
|
<string name="ConversationFragment_deleting">Suppression</string>
|
||||||
<string name="ConversationFragment_deleting_messages">Suppression des messages…</string>
|
<string name="ConversationFragment_deleting_messages">Suppression des messages…</string>
|
||||||
|
<string name="ConversationFragment_banning">Bannir</string>
|
||||||
|
<string name="ConversationFragment_banning_user">Bannissement de l’utilisateur…</string>
|
||||||
<string name="ConversationFragment_quoted_message_not_found">Le message original est introuvable</string>
|
<string name="ConversationFragment_quoted_message_not_found">Le message original est introuvable</string>
|
||||||
<string name="ConversationFragment_quoted_message_no_longer_available">Le message original n’est plus disponible</string>
|
<string name="ConversationFragment_quoted_message_no_longer_available">Le message original n’est plus disponible</string>
|
||||||
<!--ConversationListActivity-->
|
<!--ConversationListActivity-->
|
||||||
@ -1110,6 +1114,7 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i
|
|||||||
<string name="conversation_context__menu_message_details">Détails du message</string>
|
<string name="conversation_context__menu_message_details">Détails du message</string>
|
||||||
<string name="conversation_context__menu_copy_text">Copier le texte</string>
|
<string name="conversation_context__menu_copy_text">Copier le texte</string>
|
||||||
<string name="conversation_context__menu_delete_message">Supprimer le message</string>
|
<string name="conversation_context__menu_delete_message">Supprimer le message</string>
|
||||||
|
<string name="conversation_context__menu_ban_user">Bannir l’utilisateur</string>
|
||||||
<string name="conversation_context__menu_forward_message">Transférer le message</string>
|
<string name="conversation_context__menu_forward_message">Transférer le message</string>
|
||||||
<string name="conversation_context__menu_resend_message">Renvoyer le message</string>
|
<string name="conversation_context__menu_resend_message">Renvoyer le message</string>
|
||||||
<string name="conversation_context__menu_reply_to_message">Répondre au message</string>
|
<string name="conversation_context__menu_reply_to_message">Répondre au message</string>
|
||||||
@ -1413,6 +1418,7 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i
|
|||||||
<string name="activity_settings_notifications_button_title">Notifications</string>
|
<string name="activity_settings_notifications_button_title">Notifications</string>
|
||||||
<string name="activity_settings_chats_button_title">Chats</string>
|
<string name="activity_settings_chats_button_title">Chats</string>
|
||||||
<string name="activity_settings_devices_button_title">Appareils reliés</string>
|
<string name="activity_settings_devices_button_title">Appareils reliés</string>
|
||||||
|
<string name="activity_settings_invite_button_title">Inviter</string>
|
||||||
<string name="activity_settings_recovery_phrase_button_title">Phrase de récupération</string>
|
<string name="activity_settings_recovery_phrase_button_title">Phrase de récupération</string>
|
||||||
<string name="activity_settings_clear_all_data_button_title">Effacer les données</string>
|
<string name="activity_settings_clear_all_data_button_title">Effacer les données</string>
|
||||||
|
|
||||||
|
@ -1471,6 +1471,7 @@
|
|||||||
<string name="activity_settings_notifications_button_title">Уведомления</string>
|
<string name="activity_settings_notifications_button_title">Уведомления</string>
|
||||||
<string name="activity_settings_chats_button_title">Чаты</string>
|
<string name="activity_settings_chats_button_title">Чаты</string>
|
||||||
<string name="activity_settings_devices_button_title">Устройства</string>
|
<string name="activity_settings_devices_button_title">Устройства</string>
|
||||||
|
<string name="activity_settings_invite_button_title">Пригласить</string>
|
||||||
<string name="activity_settings_recovery_phrase_button_title">Секретная фраза</string>
|
<string name="activity_settings_recovery_phrase_button_title">Секретная фраза</string>
|
||||||
<string name="activity_settings_clear_all_data_button_title">Очистить данные</string>
|
<string name="activity_settings_clear_all_data_button_title">Очистить данные</string>
|
||||||
|
|
||||||
|
@ -1775,6 +1775,7 @@
|
|||||||
<string name="activity_settings_notifications_button_title">Notifications</string>
|
<string name="activity_settings_notifications_button_title">Notifications</string>
|
||||||
<string name="activity_settings_chats_button_title">Chats</string>
|
<string name="activity_settings_chats_button_title">Chats</string>
|
||||||
<string name="activity_settings_devices_button_title">Devices</string>
|
<string name="activity_settings_devices_button_title">Devices</string>
|
||||||
|
<string name="activity_settings_invite_button_title">Invite</string>
|
||||||
<string name="activity_settings_recovery_phrase_button_title">Recovery Phrase</string>
|
<string name="activity_settings_recovery_phrase_button_title">Recovery Phrase</string>
|
||||||
<string name="activity_settings_clear_all_data_button_title">Clear Data</string>
|
<string name="activity_settings_clear_all_data_button_title">Clear Data</string>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user