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.TalkBan
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.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody
@ -254,4 +255,7 @@ interface NcApiCoroutines {
@GET
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 org.greenrobot.eventbus.Subscribe
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.Collections
import java.util.Locale
@ -150,7 +155,7 @@ class ConversationInfoActivity :
get() {
if (!TextUtils.isEmpty(conversationToken)) {
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!!)
return data.build()
}
@ -192,7 +197,7 @@ class ConversationInfoActivity :
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)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
}
@ -335,6 +340,7 @@ class ConversationInfoActivity :
}
}
@SuppressLint("SetTextI18n")
private fun initViewStateObserver() {
viewModel.viewState.observe(this) { state ->
when (state) {
@ -354,6 +360,10 @@ class ConversationInfoActivity :
canGeneratePrettyURL
)
}
if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewModel.getProfileData(conversationUser, conversation!!.name)
}
}
is ConversationInfoViewModel.GetRoomErrorState -> {
@ -363,6 +373,37 @@ class ConversationInfoActivity :
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() {
@ -404,7 +445,7 @@ class ConversationInfoActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.edit) {
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
bundle.putString(KEY_ROOM_TOKEN, conversationToken)
val intent = Intent(this, ConversationInfoEditActivity::class.java)
intent.putExtras(bundle)
@ -445,7 +486,7 @@ class ConversationInfoActivity :
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
@ -538,7 +579,7 @@ class ConversationInfoActivity :
) {
binding.webinarInfoView.startTimeButtonSummary.text = (
dateUtils.getLocalDateTimeStringFromTimestamp(
conversation!!.lobbyTimer!! * DateConstants.SECOND_DIVIDER
conversation!!.lobbyTimer * DateConstants.SECOND_DIVIDER
)
)
} else {
@ -1039,7 +1080,7 @@ class ConversationInfoActivity :
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.conversationDescription.visibility = VISIBLE
}
@ -1185,7 +1226,7 @@ class ConversationInfoActivity :
val stringValue: String =
when (
DomainEnumNotificationLevelConverter()
.convertToInt(conversation!!.notificationLevel!!)
.convertToInt(conversation!!.notificationLevel)
) {
NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
@ -1234,7 +1275,7 @@ class ConversationInfoActivity :
conversation!!.name
)
) {
conversation!!.name?.let {
conversation!!.name.let {
binding.avatarImage.loadUserAvatar(
conversationUser,
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.GROUPS
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.utils.ApiUtils
import com.nextcloud.talk.utils.ApiUtils.getUrlForRooms
@ -127,6 +128,12 @@ class ConversationInfoViewModel @Inject constructor(
val createRoomViewState: LiveData<CreateRoomUIState>
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) {
_viewState.value = GetRoomStartState
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")
fun allowGuests(token: String, allow: Boolean) {
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.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import io.reactivex.Observable
interface ConversationsRepository {
@ -46,4 +47,6 @@ interface ConversationsRepository {
suspend fun clearChatHistory(apiVersion: Int, roomToken: String): GenericOverall
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.generic.GenericOverall
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.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@ -116,6 +117,10 @@ class ConversationsRepositoryImpl(
return response
}
override suspend fun getProfile(credentials: String, url: String): Profile? {
return coroutineApi.getProfile(credentials, url).ocs?.data
}
override suspend fun banActor(
credentials: String,
url: String,

View File

@ -625,4 +625,8 @@ object ApiUtils {
fun getUrlForChatMessageContext(baseUrl: String, token: String, messageId: String): String {
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_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:textStyle="bold"
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>
<LinearLayout
@ -273,12 +314,12 @@
android:id="@+id/share_conversation_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
android:paddingBottom="@dimen/standard_half_margin">
<ImageView
@ -304,18 +345,18 @@
android:id="@+id/lock_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/standard_margin"
android:paddingEnd="@dimen/standard_margin"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
android:paddingStart="@dimen/standard_margin"
android:paddingEnd="@dimen/standard_margin">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="@dimen/standard_half_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:layout_weight="1"
android:orientation="horizontal">
android:orientation="horizontal"
android:paddingTop="@dimen/standard_half_margin"
android:paddingBottom="@dimen/standard_half_margin">
<ImageView
android:layout_width="24dp"
@ -431,12 +472,12 @@
android:id="@+id/list_bans_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
android:paddingBottom="@dimen/standard_half_margin">
<ImageView
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="unarchived_conversation">Unarchived %1$s</string>
<string name="conversation_archived">Conversation is archived</string>
<string name="local_time">Local time: %1$s</string>
</resources>