Merge pull request #4950 from nextcloud/issue-4714-improve-sidebar

👤 Improving right sidebar
This commit is contained in:
Marcel Hibbe 2025-05-21 11:51:24 +00:00 committed by GitHub
commit fdfa58dcdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 267 additions and 41 deletions

View File

@ -17,6 +17,7 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall import com.nextcloud.talk.models.json.participants.AddParticipantOverall
import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.participants.TalkBanOverall import com.nextcloud.talk.models.json.participants.TalkBanOverall
import com.nextcloud.talk.models.json.profile.ProfileOverall
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody import okhttp3.MultipartBody
@ -254,4 +255,7 @@ interface NcApiCoroutines {
@GET @GET
suspend fun getNoteToSelfRoom(@Header("Authorization") authorization: String, @Url url: String): RoomOverall suspend fun getNoteToSelfRoom(@Header("Authorization") authorization: String, @Url url: String): RoomOverall
@GET
suspend fun getProfile(@Header("Authorization") authorization: String, @Url url: String): ProfileOverall
} }

View File

@ -103,6 +103,11 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Calendar import java.util.Calendar
import java.util.Collections import java.util.Collections
import java.util.Locale import java.util.Locale
@ -150,7 +155,7 @@ class ConversationInfoActivity :
get() { get() {
if (!TextUtils.isEmpty(conversationToken)) { if (!TextUtils.isEmpty(conversationToken)) {
val data = Data.Builder() val data = Data.Builder()
data.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken) data.putString(KEY_ROOM_TOKEN, conversationToken)
data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!) data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!)
return data.build() return data.build()
} }
@ -192,7 +197,7 @@ class ConversationInfoActivity :
conversationUser = currentUserProvider.currentUser.blockingGet() conversationUser = currentUserProvider.currentUser.blockingGet()
conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!! conversationToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false) hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!! credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
} }
@ -335,6 +340,7 @@ class ConversationInfoActivity :
} }
} }
@SuppressLint("SetTextI18n")
private fun initViewStateObserver() { private fun initViewStateObserver() {
viewModel.viewState.observe(this) { state -> viewModel.viewState.observe(this) { state ->
when (state) { when (state) {
@ -354,6 +360,10 @@ class ConversationInfoActivity :
canGeneratePrettyURL canGeneratePrettyURL
) )
} }
if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewModel.getProfileData(conversationUser, conversation!!.name)
}
} }
is ConversationInfoViewModel.GetRoomErrorState -> { is ConversationInfoViewModel.GetRoomErrorState -> {
@ -363,6 +373,37 @@ class ConversationInfoActivity :
else -> {} else -> {}
} }
} }
viewModel.getProfileViewState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.GetProfileSuccessState -> {
val profile = state.profile
val pronouns = profile.pronouns ?: ""
binding.pronouns.text = pronouns
val concat1 = if (profile.role != null && profile.company != null) " @ " else ""
val role = profile.role ?: ""
val company = profile.company ?: ""
val professionCompanyText = "$role$concat1$company"
binding.professionCompany.text = professionCompanyText
val profileZoneOffset = ZoneOffset.ofTotalSeconds(0)
val secondsToAdd = profile.timezoneOffset?.toLong() ?: 0
val localTime = ZonedDateTime.ofInstant(Instant.now().plusSeconds(secondsToAdd), profileZoneOffset)
val localTimeString = localTime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT))
val concat2 = if (profile.address != null) " · " else ""
val address = profile.address ?: ""
val localTimeLocation = "$localTimeString$concat2$address"
binding.locationTime.text = resources.getString(R.string.local_time, localTimeLocation)
binding.pronouns.visibility = VISIBLE
binding.professionCompany.visibility = if (professionCompanyText.isNotEmpty()) VISIBLE else GONE
binding.locationTime.visibility = VISIBLE
}
else -> {}
}
}
} }
private fun setupActionBar() { private fun setupActionBar() {
@ -404,7 +445,7 @@ class ConversationInfoActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.edit) { if (item.itemId == R.id.edit) {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken) bundle.putString(KEY_ROOM_TOKEN, conversationToken)
val intent = Intent(this, ConversationInfoEditActivity::class.java) val intent = Intent(this, ConversationInfoEditActivity::class.java)
intent.putExtras(bundle) intent.putExtras(bundle)
@ -445,7 +486,7 @@ class ConversationInfoActivity :
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName) intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken) intent.putExtra(KEY_ROOM_TOKEN, conversationToken)
intent.putExtra( intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
ConversationUtils.isParticipantOwnerOrModerator(conversation!!) ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
@ -538,7 +579,7 @@ class ConversationInfoActivity :
) { ) {
binding.webinarInfoView.startTimeButtonSummary.text = ( binding.webinarInfoView.startTimeButtonSummary.text = (
dateUtils.getLocalDateTimeStringFromTimestamp( dateUtils.getLocalDateTimeStringFromTimestamp(
conversation!!.lobbyTimer!! * DateConstants.SECOND_DIVIDER conversation!!.lobbyTimer * DateConstants.SECOND_DIVIDER
) )
) )
} else { } else {
@ -1039,7 +1080,7 @@ class ConversationInfoActivity :
binding.displayNameText.text = conversation!!.displayName binding.displayNameText.text = conversation!!.displayName
if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) { if (conversation!!.description != null && conversation!!.description.isNotEmpty()) {
binding.descriptionText.text = conversation!!.description binding.descriptionText.text = conversation!!.description
binding.conversationDescription.visibility = VISIBLE binding.conversationDescription.visibility = VISIBLE
} }
@ -1185,7 +1226,7 @@ class ConversationInfoActivity :
val stringValue: String = val stringValue: String =
when ( when (
DomainEnumNotificationLevelConverter() DomainEnumNotificationLevelConverter()
.convertToInt(conversation!!.notificationLevel!!) .convertToInt(conversation!!.notificationLevel)
) { ) {
NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always) NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention) NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
@ -1234,7 +1275,7 @@ class ConversationInfoActivity :
conversation!!.name conversation!!.name
) )
) { ) {
conversation!!.name?.let { conversation!!.name.let {
binding.avatarImage.loadUserAvatar( binding.avatarImage.loadUserAvatar(
conversationUser, conversationUser,
it, it,

View File

@ -28,6 +28,7 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.EMAILS
import com.nextcloud.talk.models.json.participants.Participant.ActorType.FEDERATED import com.nextcloud.talk.models.json.participants.Participant.ActorType.FEDERATED
import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS
import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import com.nextcloud.talk.repositories.conversations.ConversationsRepository import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ApiUtils.getUrlForRooms import com.nextcloud.talk.utils.ApiUtils.getUrlForRooms
@ -127,6 +128,12 @@ class ConversationInfoViewModel @Inject constructor(
val createRoomViewState: LiveData<CreateRoomUIState> val createRoomViewState: LiveData<CreateRoomUIState>
get() = _createRoomViewState get() = _createRoomViewState
object GetProfileErrorState : ViewState
class GetProfileSuccessState(val profile: Profile) : ViewState
private val _getProfileViewState = MutableLiveData<ViewState>()
val getProfileViewState: LiveData<ViewState>
get() = _getProfileViewState
fun getRoom(user: User, token: String) { fun getRoom(user: User, token: String) {
_viewState.value = GetRoomStartState _viewState.value = GetRoomStartState
chatNetworkDataSource.getRoom(user, token) chatNetworkDataSource.getRoom(user, token)
@ -288,6 +295,18 @@ class ConversationInfoViewModel @Inject constructor(
} }
} }
fun getProfileData(user: User, userId: String) {
val url = ApiUtils.getUrlForProfile(user.baseUrl!!, userId)
viewModelScope.launch {
val profile = conversationsRepository.getProfile(user.getCredentials(), url)
if (profile != null) {
_getProfileViewState.value = GetProfileSuccessState(profile)
} else {
_getProfileViewState.value = GetProfileErrorState
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught") @Suppress("Detekt.TooGenericExceptionCaught")
fun allowGuests(token: String, allow: Boolean) { fun allowGuests(token: String, allow: Boolean) {
viewModelScope.launch { viewModelScope.launch {

View File

@ -0,0 +1,29 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class CoreProfileAction(
@JsonField(name = ["id"])
var id: String? = null,
@JsonField(name = ["icon"])
var icon: String? = null,
@JsonField(name = ["title"])
var title: String? = null,
@JsonField(name = ["target"])
var target: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null, null, null)
}

View File

@ -0,0 +1,30 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class Profile(
@JsonField(name = ["userId"]) var userId: String? = null,
@JsonField(name = ["address"]) var address: String? = null,
@JsonField(name = ["biography"]) var biography: Int? = null,
@JsonField(name = ["displayname"]) var displayName: Int? = null,
@JsonField(name = ["headline"]) var headline: String? = null,
// @JsonField(name = ["isUserAvatarVisible"]) var isUserAvatarVisible: Boolean = false,
@JsonField(name = ["organisation"]) var company: String? = null,
@JsonField(name = ["pronouns"]) var pronouns: String? = null,
@JsonField(name = ["role"]) var role: String? = null,
@JsonField(name = ["actions"]) var actions: List<CoreProfileAction>? = null,
@JsonField(name = ["timezone"]) var timezone: String? = null,
@JsonField(name = ["timezoneOffset"]) var timezoneOffset: Int? = null
) : Parcelable

View File

@ -0,0 +1,26 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.models.json.generic.GenericMeta
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class ProfileOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta? = null,
@JsonField(name = ["data"])
var data: Profile? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -0,0 +1,23 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class ProfileOverall(
@JsonField(name = ["ocs"])
var ocs: ProfileOCS? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View File

@ -11,6 +11,7 @@ import com.nextcloud.talk.conversationinfo.CreateRoomRequest
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import io.reactivex.Observable import io.reactivex.Observable
interface ConversationsRepository { interface ConversationsRepository {
@ -46,4 +47,6 @@ interface ConversationsRepository {
suspend fun clearChatHistory(apiVersion: Int, roomToken: String): GenericOverall suspend fun clearChatHistory(apiVersion: Int, roomToken: String): GenericOverall
suspend fun createRoom(credentials: String, url: String, body: CreateRoomRequest): RoomOverall suspend fun createRoom(credentials: String, url: String, body: CreateRoomRequest): RoomOverall
suspend fun getProfile(credentials: String, url: String): Profile?
} }

View File

@ -14,6 +14,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@ -116,6 +117,10 @@ class ConversationsRepositoryImpl(
return response return response
} }
override suspend fun getProfile(credentials: String, url: String): Profile? {
return coroutineApi.getProfile(credentials, url).ocs?.data
}
override suspend fun banActor( override suspend fun banActor(
credentials: String, credentials: String,
url: String, url: String,

View File

@ -625,4 +625,8 @@ object ApiUtils {
fun getUrlForChatMessageContext(baseUrl: String, token: String, messageId: String): String { fun getUrlForChatMessageContext(baseUrl: String, token: String, messageId: String): String {
return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/chat/$token/$messageId/context" return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/chat/$token/$messageId/context"
} }
fun getUrlForProfile(baseUrl: String, userId: String): String {
return "$baseUrl$OCS_API_VERSION/profile/$userId"
}
} }

View File

@ -86,8 +86,49 @@
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements" android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size" android:textSize="@dimen/headline_text_size"
android:textStyle="bold"
tools:text="Jane Doe" /> tools:text="Jane Doe" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/pronouns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/avatar_image"
android:paddingBottom="4dp"
android:paddingTop="2dp"
android:layout_marginStart="@dimen/margin_between_elements"
android:layout_marginTop="@dimen/margin_between_elements"
android:layout_toEndOf="@id/display_name_text"
android:textSize="@dimen/supporting_text_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="She/Her"
/>
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/profession_company"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/display_name_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="Marketing Manager @ Nextcloud GmbH" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/location_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profession_company"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="10:03 PM · London" />
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
@ -269,34 +310,34 @@
android:textSize="@dimen/supporting_text_text_size" /> android:textSize="@dimen/supporting_text_text_size" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/share_conversation_button" android:id="@+id/share_conversation_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/standard_margin" android:background="?android:attr/selectableItemBackground"
android:paddingTop="@dimen/standard_half_margin" android:orientation="horizontal"
android:paddingEnd="@dimen/standard_margin" android:paddingStart="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin" android:paddingTop="@dimen/standard_half_margin"
android:orientation="horizontal" android:paddingEnd="@dimen/standard_margin"
android:background="?android:attr/selectableItemBackground"> android:paddingBottom="@dimen/standard_half_margin">
<ImageView <ImageView
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginEnd="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin"
android:contentDescription="@null" android:contentDescription="@null"
android:src="@drawable/ic_share_variant" android:src="@drawable/ic_share_variant"
app:tint="@color/grey_600" /> app:tint="@color/grey_600" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/nc_guest_access_share_link" android:text="@string/nc_guest_access_share_link"
android:textSize="@dimen/headline_text_size" /> android:textSize="@dimen/headline_text_size" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -304,18 +345,18 @@
android:id="@+id/lock_conversation" android:id="@+id/lock_conversation"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/standard_margin" android:background="?android:attr/selectableItemBackground"
android:paddingEnd="@dimen/standard_margin"
android:orientation="horizontal" android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"> android:paddingStart="@dimen/standard_margin"
android:paddingEnd="@dimen/standard_margin">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/standard_half_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:layout_weight="1" android:layout_weight="1"
android:orientation="horizontal"> android:orientation="horizontal"
android:paddingTop="@dimen/standard_half_margin"
android:paddingBottom="@dimen/standard_half_margin">
<ImageView <ImageView
android:layout_width="24dp" android:layout_width="24dp"
@ -431,12 +472,12 @@
android:id="@+id/list_bans_button" android:id="@+id/list_bans_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin" android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin" android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin" android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin" android:paddingBottom="@dimen/standard_half_margin">
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<ImageView <ImageView
android:layout_width="24dp" android:layout_width="24dp"

View File

@ -847,4 +847,5 @@ How to translate with transifex:
<string name="archived_conversation">Archived %1$s</string> <string name="archived_conversation">Archived %1$s</string>
<string name="unarchived_conversation">Unarchived %1$s</string> <string name="unarchived_conversation">Unarchived %1$s</string>
<string name="conversation_archived">Conversation is archived</string> <string name="conversation_archived">Conversation is archived</string>
<string name="local_time">Local time: %1$s</string>
</resources> </resources>