Move setting to room profile

This commit is contained in:
Valere 2022-10-04 17:17:01 +02:00
parent 8de2fe8917
commit 68d4ac34c7
16 changed files with 173 additions and 124 deletions

View File

@ -1236,7 +1236,7 @@
<string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string>
<string name="encryption_never_send_to_unverified_devices_in_room">Never send encrypted messages to unverified sessions in this room.</string>
<string name="some_devices_will_not_be_able_to_decrypt">⚠ There are unverified devices in this room, they wont be able to decrypt messages you send.</string>
<string name="room_settings_global_blacklist_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string>
<string name="room_settings_global_block_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string>
<plurals name="encryption_import_room_keys_success">
<item quantity="one">%1$d/%2$d key imported with success.</item>
<item quantity="other">%1$d/%2$d keys imported with success.</item>

View File

@ -61,7 +61,7 @@ interface CryptoService {
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean>
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
fun setWarnOnUnknownDevices(warn: Boolean)

View File

@ -1177,7 +1177,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return true if the client should encrypt messages only for the verified devices.
*/
override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean {
return roomId?.let { cryptoStore.getBlacklistUnverifiedDevices(roomId) }
return roomId?.let { cryptoStore.getBlockUnverifiedDevices(roomId) }
?: false
}
@ -1186,8 +1186,8 @@ internal class DefaultCryptoService @Inject constructor(
*
* @return Live status
*/
override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> {
return cryptoStore.getLiveBlacklistUnverifiedDevices(roomId)
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
return cryptoStore.getLiveBlockUnverifiedDevices(roomId)
}
/**

View File

@ -424,7 +424,7 @@ internal class MXMegolmEncryption(
// an m.new_device.
val keys = deviceListManager.downloadKeys(userIds, false)
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() ||
cryptoStore.getBlacklistUnverifiedDevices(roomId)
cryptoStore.getBlockUnverifiedDevices(roomId)
val devicesInRoom = DeviceInRoomInfo()
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()

View File

@ -125,14 +125,14 @@ internal interface IMXCryptoStore {
*
* @return Live status
*/
fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean>
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
/**
* Tell if unverified devices should be blacklisted when sending keys.
*
* @return true if should not send keys to unverified devices
*/
fun getBlacklistUnverifiedDevices(roomId: String): Boolean
fun getBlockUnverifiedDevices(roomId: String): Boolean
/**
* Define if encryption keys should be sent to unverified devices in this room.

View File

@ -1097,7 +1097,7 @@ internal class RealmCryptoStore @Inject constructor(
}
}
override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> {
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<CryptoRoomEntity>()
@ -1112,7 +1112,7 @@ internal class RealmCryptoStore @Inject constructor(
}
}
override fun getBlacklistUnverifiedDevices(roomId: String): Boolean {
override fun getBlockUnverifiedDevices(roomId: String): Boolean {
return doWithRealm(realmConfiguration) { realm ->
realm.where<CryptoRoomEntity>()
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)

View File

@ -27,4 +27,5 @@ sealed class RoomProfileAction : VectorViewModelAction {
object ShareRoomProfile : RoomProfileAction()
object CreateShortcut : RoomProfileAction()
object RestoreEncryptionState : RoomProfileAction()
data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomProfileAction()
}

View File

@ -27,6 +27,7 @@ import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericPositiveButtonItem
import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.home.ShortcutCreator
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
@ -66,6 +67,8 @@ class RoomProfileController @Inject constructor(
fun onUrlInTopicLongClicked(url: String)
fun doMigrateToVersion(newVersion: String)
fun restoreEncryptionState()
fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean)
fun openGlobalBlockSettings()
}
override fun buildModels(data: RoomProfileViewState?) {
@ -175,6 +178,53 @@ class RoomProfileController @Inject constructor(
}
buildEncryptionAction(data.actionPermissions, roomSummary)
if (roomSummary.isEncrypted && !encryptionMisconfigured) {
data.globalCryptoConfig.invoke()?.let { globalConfig ->
if (globalConfig.globalBlockUnverifiedDevices) {
genericFooterItem {
id("globalConfig")
centered(false)
text(
span {
+host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text)
apply {
if (data.unverifiedDevicesInTheRoom.invoke() == true) {
+"\n"
+host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
}
}
}.toEpoxyCharSequence()
)
itemClickAction {
host.callback?.openGlobalBlockSettings()
}
}
} else {
// per room setting is available
val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke()
formSwitchItem {
id("send_to_unverified")
enabled(shouldBlockUnverified != null)
title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room))
switchChecked(shouldBlockUnverified ?: false)
apply {
if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) {
summary(
host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
)
} else {
summary(null)
}
}
listener { value ->
host.callback?.setEncryptedToVerifiedDevicesOnly(value)
}
}
}
}
}
// More
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(

View File

@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.navigation.SettingsActivityPayload
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
@ -346,6 +347,14 @@ class RoomProfileFragment :
)
}
override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) {
roomProfileViewModel.handle(RoomProfileAction.SetEncryptToVerifiedDeviceOnly(enabled))
}
override fun openGlobalBlockSettings() {
navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy)
}
private fun onAvatarClicked(view: View) = withState(roomProfileViewModel) { state ->
state.roomSummary()?.toMatrixItem()?.let { matrixItem ->
navigator.openBigImageViewer(requireActivity(), view, matrixItem)

View File

@ -17,6 +17,7 @@
package im.vector.app.features.roomprofile
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -32,7 +33,11 @@ import im.vector.app.features.home.ShortcutCreator
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
@ -76,6 +81,45 @@ class RoomProfileViewModel @AssistedInject constructor(
observeBannedRoomMembers(flowRoom)
observePermissions()
observePowerLevels()
observeCryptoSettings(flowRoom)
}
private fun observeCryptoSettings(flowRoom: FlowRoom) {
val perRoomBlockStatus = session.cryptoService().getLiveBlockUnverifiedDevices(initialState.roomId)
.asFlow()
perRoomBlockStatus
.execute {
copy(encryptToVerifiedDeviceOnly = it)
}
val globalBlockStatus = session.cryptoService().getLiveGlobalCryptoConfig()
.asFlow()
globalBlockStatus
.execute {
copy(globalCryptoConfig = it)
}
perRoomBlockStatus.combine(globalBlockStatus) { perRoom, global ->
perRoom || global.globalBlockUnverifiedDevices
}.flatMapLatest {
if (it) {
flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() })
.map { it.map { it.userId } }
.flatMapLatest {
session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow()
}
} else {
flowOf(emptyList())
}
}.map {
it.isNotEmpty()
}.execute {
copy(
unverifiedDevicesInTheRoom = it
)
}
}
private fun observePowerLevels() {
@ -141,6 +185,7 @@ class RoomProfileViewModel @AssistedInject constructor(
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
RoomProfileAction.CreateShortcut -> handleCreateShortcut()
RoomProfileAction.RestoreEncryptionState -> restoreEncryptionState()
is RoomProfileAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled)
}
}
@ -212,6 +257,12 @@ class RoomProfileViewModel @AssistedInject constructor(
}
}
private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) {
session.coroutineScope.launch {
session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled)
}
}
private fun restoreEncryptionState() {
_viewEvents.post(RoomProfileViewEvents.Loading())
session.coroutineScope.launch {

View File

@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
@ -36,6 +37,9 @@ data class RoomProfileViewState(
val canUpgradeRoom: Boolean = false,
val isTombstoned: Boolean = false,
val canUpdateRoomState: Boolean = false,
val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized,
val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized,
val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized,
) : MavericksState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)

View File

@ -28,7 +28,6 @@ sealed class RoomSettingsAction : VectorViewModelAction {
data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction()
data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules) : RoomSettingsAction()
data class SetRoomGuestAccess(val guestAccess: GuestAccess) : RoomSettingsAction()
data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomSettingsAction()
object Save : RoomSettingsAction()
object Cancel : RoomSettingsAction()

View File

@ -22,7 +22,6 @@ import im.vector.app.core.epoxy.dividerItem
import im.vector.app.core.epoxy.profiles.buildProfileAction
import im.vector.app.core.epoxy.profiles.buildProfileSection
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.verticalMarginItem
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.form.formEditTextItem
@ -31,8 +30,6 @@ import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.util.toMatrixItem
@ -55,8 +52,6 @@ class RoomSettingsController @Inject constructor(
fun onHistoryVisibilityClicked()
fun onJoinRuleClicked()
fun onToggleGuestAccess()
fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean)
fun openGlobalBlockSettings()
}
var callback: Callback? = null
@ -150,54 +145,54 @@ class RoomSettingsController @Inject constructor(
id("guestAccessDivider")
}
}
//
// // Security
// buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
// Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
data.globalCryptoConfig.invoke()?.let { globalConfig ->
if (globalConfig.globalBlockUnverifiedDevices) {
genericFooterItem {
id("globalConfig")
centered(false)
text(
span {
+host.stringProvider.getString(R.string.room_settings_global_blacklist_unverified_info_text)
apply {
if (data.unverifiedDevicesInTheRoom.invoke() == true) {
+"\n"
+host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
}
}
}.toEpoxyCharSequence()
)
itemClickAction {
host.callback?.openGlobalBlockSettings()
}
}
} else {
// per room setting is available
val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke()
formSwitchItem {
id("send_to_unverified")
enabled(shouldBlockUnverified != null)
title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room))
switchChecked(shouldBlockUnverified ?: false)
apply {
if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) {
summary(
host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
)
} else {
summary(null)
}
}
listener { value ->
host.callback?.setEncryptedToVerifiedDevicesOnly(value)
}
}
}
}
// data.globalCryptoConfig.invoke()?.let { globalConfig ->
// if (globalConfig.globalBlockUnverifiedDevices) {
// genericFooterItem {
// id("globalConfig")
// centered(false)
// text(
// span {
// +host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text)
// apply {
// if (data.unverifiedDevicesInTheRoom.invoke() == true) {
// +"\n"
// +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
// }
// }
// }.toEpoxyCharSequence()
// )
// itemClickAction {
// host.callback?.openGlobalBlockSettings()
// }
// }
// } else {
// // per room setting is available
// val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke()
// formSwitchItem {
// id("send_to_unverified")
// enabled(shouldBlockUnverified != null)
// title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room))
//
// switchChecked(shouldBlockUnverified ?: false)
//
// apply {
// if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) {
// summary(
// host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
// )
// } else {
// summary(null)
// }
// }
// listener { value ->
// host.callback?.setEncryptedToVerifiedDevicesOnly(value)
// }
// }
// }
// }
}
}

View File

@ -43,7 +43,6 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.FragmentRoomSettingGenericBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
@ -200,14 +199,6 @@ class RoomSettingsFragment :
viewModel.handle(RoomSettingsAction.SetRoomGuestAccess(toggled))
}
override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) {
viewModel.handle(RoomSettingsAction.SetEncryptToVerifiedDeviceOnly(enabled))
}
override fun openGlobalBlockSettings() {
navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy)
}
override fun onImageReady(uri: Uri?) {
uri ?: return
viewModel.handle(

View File

@ -17,7 +17,6 @@
package im.vector.app.features.roomprofile.settings
import androidx.core.net.toFile
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -26,12 +25,8 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -42,8 +37,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
@ -90,39 +83,6 @@ class RoomSettingsViewModel @AssistedInject constructor(
canUpgradeToRestricted = couldUpgradeToRestricted
)
}
session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId)
.asFlow()
.execute {
copy(encryptToVerifiedDeviceOnly = it)
}
session.cryptoService().getLiveGlobalCryptoConfig()
.asFlow()
.execute {
copy(globalCryptoConfig = it)
}
val flowRoom = room.flow()
session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId)
.asFlow()
.flatMapLatest {
if (it) {
flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() })
.map { it.map { it.userId } }
.flatMapLatest {
session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow()
}
} else {
flowOf(emptyList())
}
}.map {
it.isNotEmpty()
}.execute {
copy(
unverifiedDevicesInTheRoom = it
)
}
}
private fun observeState() {
@ -252,7 +212,6 @@ class RoomSettingsViewModel @AssistedInject constructor(
is RoomSettingsAction.SetRoomGuestAccess -> handleSetGuestAccess(action)
is RoomSettingsAction.Save -> saveSettings()
is RoomSettingsAction.Cancel -> cancel()
is RoomSettingsAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled)
}
}
@ -274,12 +233,6 @@ class RoomSettingsViewModel @AssistedInject constructor(
}
}
private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) {
session.coroutineScope.launch {
session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled)
}
}
private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) {
setState {
deletePendingAvatar(this)

View File

@ -23,7 +23,6 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
@ -47,9 +46,6 @@ data class RoomSettingsViewState(
val actionPermissions: ActionPermissions = ActionPermissions(),
val supportsRestricted: Boolean = false,
val canUpgradeToRestricted: Boolean = false,
val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized,
val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized,
val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized,
) : MavericksState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)