Merge pull request #2614 from mitchnull/feature/issue-2610-override-nick-color-via-user-account-data
[issue-2610] implement setting to override nick color
This commit is contained in:
commit
7ce9eceb7c
|
@ -581,6 +581,7 @@ Changes in Element 1.1.7 (2021-05-12)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
- Allow changing nick colors (#2610)
|
||||||
- Spaces beta
|
- Spaces beta
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
|
|
|
@ -27,4 +27,5 @@ object UserAccountDataTypes {
|
||||||
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
||||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||||
|
const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,13 @@ import androidx.lifecycle.LifecycleOwner
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.utils.BehaviorDataSource
|
import im.vector.app.core.utils.BehaviorDataSource
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.rxkotlin.addTo
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -33,8 +38,11 @@ import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.rx.rx
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -54,6 +62,7 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppStateHandler @Inject constructor(
|
class AppStateHandler @Inject constructor(
|
||||||
private val sessionDataSource: ActiveSessionDataSource,
|
private val sessionDataSource: ActiveSessionDataSource,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||||
private val uiStateRepository: UiStateRepository,
|
private val uiStateRepository: UiStateRepository,
|
||||||
private val activeSessionHolder: ActiveSessionHolder
|
private val activeSessionHolder: ActiveSessionHolder
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
@ -132,6 +141,11 @@ class AppStateHandler @Inject constructor(
|
||||||
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
|
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
|
fun entersForeground() {
|
||||||
|
observeUserAccountData()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
observeActiveSession()
|
observeActiveSession()
|
||||||
}
|
}
|
||||||
|
@ -150,4 +164,19 @@ class AppStateHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeUserAccountData() {
|
||||||
|
sessionDataSource.observe()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.switchMap {
|
||||||
|
it.orNull()?.rx()?.liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS))
|
||||||
|
?: Observable.just(emptyList())
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe {
|
||||||
|
val overrideColorSpecs = it?.firstOrNull()?.content?.toModel<Map<String, String>>()
|
||||||
|
matrixItemColorProvider.setOverrideColors(overrideColorSpecs)
|
||||||
|
}
|
||||||
|
.addTo(compositeDisposable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,39 @@ class MatrixItemColorProvider @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setOverrideColors(overrideColors: Map<String, String>?) {
|
||||||
|
overrideColors?.forEach() {
|
||||||
|
setOverrideColor(it.key, it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOverrideColor(id: String, colorSpec: String?) : Boolean {
|
||||||
|
val color = parseUserColorSpec(colorSpec)
|
||||||
|
if (color == null) {
|
||||||
|
cache.remove(id)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
cache.put(id, color)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private fun parseUserColorSpec(colorText: String?): Int? {
|
||||||
|
if (colorText.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (colorText.first() == '#') {
|
||||||
|
return (colorText.substring(1).toLong(radix = 16) or 0xff000000L).toInt()
|
||||||
|
} else {
|
||||||
|
return colorProvider.getColor(getUserColorByIndex(colorText.toInt()))
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ColorRes
|
@ColorRes
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -52,7 +85,12 @@ class MatrixItemColorProvider @Inject constructor(
|
||||||
|
|
||||||
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.code }
|
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.code }
|
||||||
|
|
||||||
return when (abs(hash) % 8) {
|
return getUserColorByIndex(abs(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
private fun getUserColorByIndex(index: Int): Int {
|
||||||
|
return when (index % 8) {
|
||||||
1 -> R.color.element_name_02
|
1 -> R.color.element_name_02
|
||||||
2 -> R.color.element_name_03
|
2 -> R.color.element_name_03
|
||||||
3 -> R.color.element_name_04
|
3 -> R.color.element_name_04
|
||||||
|
|
|
@ -42,6 +42,7 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
fun onShowDeviceList()
|
fun onShowDeviceList()
|
||||||
fun onShowDeviceListNoCrossSigning()
|
fun onShowDeviceListNoCrossSigning()
|
||||||
fun onOpenDmClicked()
|
fun onOpenDmClicked()
|
||||||
|
fun onOverrideColorClicked()
|
||||||
fun onJumpToReadReceiptClicked()
|
fun onJumpToReadReceiptClicked()
|
||||||
fun onMentionClicked()
|
fun onMentionClicked()
|
||||||
fun onEditPowerLevel(currentRole: Role)
|
fun onEditPowerLevel(currentRole: Role)
|
||||||
|
@ -171,11 +172,19 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
|
|
||||||
private fun buildMoreSection(state: RoomMemberProfileViewState) {
|
private fun buildMoreSection(state: RoomMemberProfileViewState) {
|
||||||
// More
|
// More
|
||||||
|
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||||
|
|
||||||
|
buildProfileAction(
|
||||||
|
id = "overrideColor",
|
||||||
|
editable = false,
|
||||||
|
title = stringProvider.getString(R.string.room_member_override_nick_color),
|
||||||
|
divider = false,
|
||||||
|
action = { callback?.onOverrideColorClicked() }
|
||||||
|
)
|
||||||
|
|
||||||
if (!state.isMine) {
|
if (!state.isMine) {
|
||||||
val membership = state.asyncMembership() ?: return
|
val membership = state.asyncMembership() ?: return
|
||||||
|
|
||||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
|
||||||
|
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "direct",
|
id = "direct",
|
||||||
editable = false,
|
editable = false,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
@ -43,6 +44,7 @@ import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.platform.StateView
|
import im.vector.app.core.platform.StateView
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||||
|
import im.vector.app.databinding.DialogBaseEditTextBinding
|
||||||
import im.vector.app.databinding.DialogShareQrCodeBinding
|
import im.vector.app.databinding.DialogShareQrCodeBinding
|
||||||
import im.vector.app.databinding.FragmentMatrixProfileBinding
|
import im.vector.app.databinding.FragmentMatrixProfileBinding
|
||||||
import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding
|
import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding
|
||||||
|
@ -51,10 +53,13 @@ import im.vector.app.features.displayname.getBestName
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailPendingAction
|
import im.vector.app.features.home.room.detail.RoomDetailPendingAction
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
|
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
|
||||||
import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs
|
import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -68,7 +73,8 @@ data class RoomMemberProfileArgs(
|
||||||
class RoomMemberProfileFragment @Inject constructor(
|
class RoomMemberProfileFragment @Inject constructor(
|
||||||
private val roomMemberProfileController: RoomMemberProfileController,
|
private val roomMemberProfileController: RoomMemberProfileController,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val roomDetailPendingActionStore: RoomDetailPendingActionStore
|
private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
) : VectorBaseFragment<FragmentMatrixProfileBinding>(),
|
) : VectorBaseFragment<FragmentMatrixProfileBinding>(),
|
||||||
RoomMemberProfileController.Callback {
|
RoomMemberProfileController.Callback {
|
||||||
|
|
||||||
|
@ -200,6 +206,7 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
headerViews.memberProfileIdView.text = userMatrixItem.id
|
headerViews.memberProfileIdView.text = userMatrixItem.id
|
||||||
val bestName = userMatrixItem.getBestName()
|
val bestName = userMatrixItem.getBestName()
|
||||||
headerViews.memberProfileNameView.text = bestName
|
headerViews.memberProfileNameView.text = bestName
|
||||||
|
headerViews.memberProfileNameView.setTextColor(matrixItemColorProvider.getColor(userMatrixItem))
|
||||||
views.matrixProfileToolbarTitleView.text = bestName
|
views.matrixProfileToolbarTitleView.text = bestName
|
||||||
avatarRenderer.render(userMatrixItem, headerViews.memberProfileAvatarView)
|
avatarRenderer.render(userMatrixItem, headerViews.memberProfileAvatarView)
|
||||||
avatarRenderer.render(userMatrixItem, views.matrixProfileToolbarAvatarImageView)
|
avatarRenderer.render(userMatrixItem, views.matrixProfileToolbarAvatarImageView)
|
||||||
|
@ -321,6 +328,42 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
navigator.openBigImageViewer(requireActivity(), view, userMatrixItem)
|
navigator.openBigImageViewer(requireActivity(), view, userMatrixItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOverrideColorClicked(): Unit = withState(viewModel) { state ->
|
||||||
|
val inflater = requireActivity().layoutInflater
|
||||||
|
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
||||||
|
val views = DialogBaseEditTextBinding.bind(layout)
|
||||||
|
val session = injector().activeSessionHolder().getActiveSession()
|
||||||
|
val overrideColorsSetting = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)
|
||||||
|
val overrideColorSpecs = overrideColorsSetting?.content?.toMap().orEmpty()
|
||||||
|
val userId = state.userId
|
||||||
|
val overrideColorSpec : String? = overrideColorSpecs[userId]?.toString()
|
||||||
|
views.editText.setText(overrideColorSpec)
|
||||||
|
views.editText.hint = "#000000"
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.room_member_override_nick_color)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
val newOverrideColorSpec = views.editText.text.toString()
|
||||||
|
if (newOverrideColorSpec != overrideColorSpec) {
|
||||||
|
val newOverrideColorSpecs = overrideColorSpecs.toMutableMap()
|
||||||
|
if (matrixItemColorProvider.setOverrideColor(userId, newOverrideColorSpec)) {
|
||||||
|
newOverrideColorSpecs[userId] = newOverrideColorSpec
|
||||||
|
} else {
|
||||||
|
newOverrideColorSpecs.remove(userId)
|
||||||
|
}
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
session.accountDataService().updateUserAccountData(
|
||||||
|
type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS,
|
||||||
|
content = newOverrideColorSpecs)
|
||||||
|
}
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEditPowerLevel(currentRole: Role) {
|
override fun onEditPowerLevel(currentRole: Role) {
|
||||||
EditPowerLevelDialogs.showChoice(requireActivity(), R.string.power_level_edit_title, currentRole) { newPowerLevel ->
|
EditPowerLevelDialogs.showChoice(requireActivity(), R.string.power_level_edit_title, currentRole) { newPowerLevel ->
|
||||||
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true))
|
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true))
|
||||||
|
|
|
@ -2812,6 +2812,8 @@
|
||||||
<string name="direct_room_profile_section_more_leave">Leave</string>
|
<string name="direct_room_profile_section_more_leave">Leave</string>
|
||||||
<string name="room_profile_leaving_room">"Leaving the room…"</string>
|
<string name="room_profile_leaving_room">"Leaving the room…"</string>
|
||||||
|
|
||||||
|
<string name="room_member_override_nick_color">Override nick color</string>
|
||||||
|
|
||||||
<string name="room_member_power_level_admins">Admins</string>
|
<string name="room_member_power_level_admins">Admins</string>
|
||||||
<string name="room_member_power_level_moderators">Moderators</string>
|
<string name="room_member_power_level_moderators">Moderators</string>
|
||||||
<string name="room_member_power_level_custom">Custom</string>
|
<string name="room_member_power_level_custom">Custom</string>
|
||||||
|
|
Loading…
Reference in New Issue