mirror of
https://github.com/element-hq/element-android
synced 2024-09-06 17:05:20 +00:00
create encrypted DM for user invite by email (#8172)
Co-authored-by: jonnyandrew <jonny.andrew@protonmail.com>
This commit is contained in:
parent
29f2bf25fc
commit
94675b9f85
1
changelog.d/6912.misc
Normal file
1
changelog.d/6912.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Direct Message: Manage encrypted DM in case of invite by email
|
|
@ -1817,6 +1817,7 @@
|
||||||
<string name="add_by_qr_code">Add by QR code</string>
|
<string name="add_by_qr_code">Add by QR code</string>
|
||||||
<string name="qr_code">QR code</string>
|
<string name="qr_code">QR code</string>
|
||||||
<string name="creating_direct_room">"Creating room…"</string>
|
<string name="creating_direct_room">"Creating room…"</string>
|
||||||
|
<string name="direct_room_user_list_only_invite_one_email">You can only invite one email at a time</string>
|
||||||
<string name="direct_room_user_list_known_title">Known Users</string>
|
<string name="direct_room_user_list_known_title">Known Users</string>
|
||||||
<string name="direct_room_user_list_suggestions_title">Suggestions</string>
|
<string name="direct_room_user_list_suggestions_title">Suggestions</string>
|
||||||
|
|
||||||
|
@ -2561,6 +2562,8 @@
|
||||||
<string name="encryption_enabled_tile_description">Messages in this room are end-to-end encrypted. Learn more & verify users in their profile.</string>
|
<string name="encryption_enabled_tile_description">Messages in this room are end-to-end encrypted. Learn more & verify users in their profile.</string>
|
||||||
<string name="direct_room_encryption_enabled_tile_description">Messages in this chat are end-to-end encrypted.</string>
|
<string name="direct_room_encryption_enabled_tile_description">Messages in this chat are end-to-end encrypted.</string>
|
||||||
<string name="direct_room_encryption_enabled_tile_description_future">Messages in this chat will be end-to-end encrypted.</string>
|
<string name="direct_room_encryption_enabled_tile_description_future">Messages in this chat will be end-to-end encrypted.</string>
|
||||||
|
<string name="direct_room_encryption_enabled_waiting_users">Waiting for users to join ${app_name}</string>
|
||||||
|
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Once invited users have joined ${app_name}, you will be able to chat and the room will be end-to-end encrypted</string>
|
||||||
<string name="encryption_not_enabled">Encryption not enabled</string>
|
<string name="encryption_not_enabled">Encryption not enabled</string>
|
||||||
<string name="encryption_misconfigured">Encryption is misconfigured</string>
|
<string name="encryption_misconfigured">Encryption is misconfigured</string>
|
||||||
<string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string>
|
<string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string>
|
||||||
|
|
|
@ -190,10 +190,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
|
private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
|
||||||
return params.enableEncryptionIfInvitedUsersSupportIt &&
|
return params.enableEncryptionIfInvitedUsersSupportIt &&
|
||||||
// Parity with web, enable if users have encryption ready devices
|
// Parity with web, enable if users have encryption ready devices
|
||||||
// for now remove checks on cross signing and 3pid invites
|
// for now remove checks on cross signing
|
||||||
// && crossSigningService.isCrossSigningVerified()
|
// && crossSigningService.isCrossSigningVerified()
|
||||||
params.invite3pids.isEmpty() &&
|
|
||||||
params.invitedUserIds.isNotEmpty() &&
|
|
||||||
params.invitedUserIds.let { userIds ->
|
params.invitedUserIds.let { userIds ->
|
||||||
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
|
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,12 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
val leftMembersNames = roomMembers.queryLeftRoomMembersEvent()
|
val leftMembersNames = roomMembers.queryLeftRoomMembersEvent()
|
||||||
.findAll()
|
.findAll()
|
||||||
.map { displayNameResolver.getBestName(it.toMatrixItem()) }
|
.map { displayNameResolver.getBestName(it.toMatrixItem()) }
|
||||||
roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames)
|
val directUserId = roomSummary?.directUserId
|
||||||
|
if (!directUserId.isNullOrBlank() && leftMembersNames.isEmpty()) {
|
||||||
|
directUserId
|
||||||
|
} else {
|
||||||
|
roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
roomDisplayNameFallbackProvider.getNameFor1member(
|
roomDisplayNameFallbackProvider.getNameFor1member(
|
||||||
|
|
|
@ -57,7 +57,6 @@ import im.vector.app.features.home.room.list.RoomListViewModel
|
||||||
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
|
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
|
||||||
import im.vector.app.features.home.room.list.home.invites.InvitesViewModel
|
import im.vector.app.features.home.room.list.home.invites.InvitesViewModel
|
||||||
import im.vector.app.features.home.room.list.home.release.ReleaseNotesViewModel
|
import im.vector.app.features.home.room.list.home.release.ReleaseNotesViewModel
|
||||||
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
|
|
||||||
import im.vector.app.features.invite.InviteUsersToRoomViewModel
|
import im.vector.app.features.invite.InviteUsersToRoomViewModel
|
||||||
import im.vector.app.features.location.LocationSharingViewModel
|
import im.vector.app.features.location.LocationSharingViewModel
|
||||||
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
|
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
|
||||||
|
@ -500,11 +499,6 @@ interface MavericksViewModelModule {
|
||||||
@MavericksViewModelKey(StartAppViewModel::class)
|
@MavericksViewModelKey(StartAppViewModel::class)
|
||||||
fun startAppViewModelFactory(factory: StartAppViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun startAppViewModelFactory(factory: StartAppViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@MavericksViewModelKey(HomeServerCapabilitiesViewModel::class)
|
|
||||||
fun homeServerCapabilitiesViewModelFactory(factory: HomeServerCapabilitiesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(InviteUsersToRoomViewModel::class)
|
@MavericksViewModelKey(InviteUsersToRoomViewModel::class)
|
||||||
|
|
|
@ -93,6 +93,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
title = getString(R.string.fab_menu_create_chat),
|
title = getString(R.string.fab_menu_create_chat),
|
||||||
menuResId = R.menu.vector_create_direct_room,
|
menuResId = R.menu.vector_create_direct_room,
|
||||||
submitMenuItemId = R.id.action_create_direct_room,
|
submitMenuItemId = R.id.action_create_direct_room,
|
||||||
|
single3pidSelection = true,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = runCatchingToAsync {
|
val result = runCatchingToAsync {
|
||||||
if (vectorPreferences.isDeferredDmEnabled()) {
|
if (vectorPreferences.isDeferredDmEnabled() && roomParams.invite3pids.isEmpty()) {
|
||||||
session.roomService().createLocalRoom(roomParams)
|
session.roomService().createLocalRoom(roomParams)
|
||||||
} else {
|
} else {
|
||||||
analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
|
analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
|
||||||
|
|
|
@ -1176,6 +1176,10 @@ class TimelineFragment :
|
||||||
views.hideComposerViews()
|
views.hideComposerViews()
|
||||||
views.notificationAreaView.render(NotificationAreaView.State.Tombstone(mainState.tombstoneEvent))
|
views.notificationAreaView.render(NotificationAreaView.State.Tombstone(mainState.tombstoneEvent))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (summary.isDirect && summary.isEncrypted && summary.joinedMembersCount == 1 && summary.invitedMembersCount == 0) {
|
||||||
|
views.hideComposerViews()
|
||||||
|
}
|
||||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||||
views.hideComposerViews()
|
views.hideComposerViews()
|
||||||
lazyLoadedViews.inviteView(true)?.apply {
|
lazyLoadedViews.inviteView(true)?.apply {
|
||||||
|
|
|
@ -56,22 +56,37 @@ class EncryptionItemFactory @Inject constructor(
|
||||||
val description: String
|
val description: String
|
||||||
val shield: StatusTileTimelineItem.ShieldUIState
|
val shield: StatusTileTimelineItem.ShieldUIState
|
||||||
if (isSafeAlgorithm) {
|
if (isSafeAlgorithm) {
|
||||||
val isDirect = session.getRoomSummary(event.root.roomId.orEmpty())?.isDirect.orFalse()
|
val roomSummary = session.getRoomSummary(event.root.roomId.orEmpty())
|
||||||
title = stringProvider.getString(R.string.encryption_enabled)
|
val isDirect = roomSummary?.isDirect.orFalse()
|
||||||
description = stringProvider.getString(
|
val (resTitle, resDescription, resShield) = when {
|
||||||
|
isDirect -> {
|
||||||
|
val isWaitingUser = roomSummary?.isEncrypted.orFalse() && roomSummary?.joinedMembersCount == 1 && roomSummary.invitedMembersCount == 0
|
||||||
when {
|
when {
|
||||||
isDirect && RoomLocalEcho.isLocalEchoId(event.root.roomId.orEmpty()) -> {
|
RoomLocalEcho.isLocalEchoId(event.root.roomId.orEmpty()) -> Triple(
|
||||||
R.string.direct_room_encryption_enabled_tile_description_future
|
R.string.encryption_enabled,
|
||||||
}
|
R.string.direct_room_encryption_enabled_tile_description_future,
|
||||||
isDirect -> {
|
StatusTileTimelineItem.ShieldUIState.BLACK
|
||||||
R.string.direct_room_encryption_enabled_tile_description
|
)
|
||||||
}
|
isWaitingUser -> Triple(
|
||||||
else -> {
|
R.string.direct_room_encryption_enabled_waiting_users,
|
||||||
R.string.encryption_enabled_tile_description
|
R.string.direct_room_encryption_enabled_waiting_users_tile_description,
|
||||||
}
|
StatusTileTimelineItem.ShieldUIState.WAITING
|
||||||
|
)
|
||||||
|
else -> Triple(
|
||||||
|
R.string.encryption_enabled,
|
||||||
|
R.string.direct_room_encryption_enabled_tile_description,
|
||||||
|
StatusTileTimelineItem.ShieldUIState.BLACK
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
shield = StatusTileTimelineItem.ShieldUIState.BLACK
|
else -> {
|
||||||
|
Triple(R.string.encryption_enabled, R.string.encryption_enabled_tile_description, StatusTileTimelineItem.ShieldUIState.BLACK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
title = stringProvider.getString(resTitle)
|
||||||
|
description = stringProvider.getString(resDescription)
|
||||||
|
shield = resShield
|
||||||
} else {
|
} else {
|
||||||
title = stringProvider.getString(R.string.encryption_misconfigured)
|
title = stringProvider.getString(R.string.encryption_misconfigured)
|
||||||
description = stringProvider.getString(R.string.encryption_unknown_algorithm_tile_description)
|
description = stringProvider.getString(R.string.encryption_unknown_algorithm_tile_description)
|
||||||
|
|
|
@ -40,6 +40,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
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.api.session.room.model.localecho.RoomLocalEcho
|
import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
@ -127,26 +128,38 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderE2ESecureTile(holder: Holder) {
|
private fun renderE2ESecureTile(holder: Holder) {
|
||||||
val resources = holder.expandView.resources
|
val (title, description, drawable) = when {
|
||||||
val description = when {
|
|
||||||
isDirectRoom -> {
|
isDirectRoom -> {
|
||||||
if (attributes.isLocalRoom) {
|
val isWaitingUser = roomSummary?.isEncrypted.orFalse() && roomSummary?.joinedMembersCount == 1 && roomSummary?.invitedMembersCount == 0
|
||||||
resources.getString(R.string.direct_room_encryption_enabled_tile_description_future)
|
when {
|
||||||
} else {
|
attributes.isLocalRoom -> Triple(
|
||||||
resources.getString(R.string.direct_room_encryption_enabled_tile_description)
|
R.string.encryption_enabled,
|
||||||
|
R.string.direct_room_encryption_enabled_tile_description_future,
|
||||||
|
R.drawable.ic_shield_black
|
||||||
|
)
|
||||||
|
isWaitingUser -> Triple(
|
||||||
|
R.string.direct_room_encryption_enabled_waiting_users,
|
||||||
|
R.string.direct_room_encryption_enabled_waiting_users_tile_description,
|
||||||
|
R.drawable.ic_room_profile_member_list
|
||||||
|
)
|
||||||
|
else -> Triple(
|
||||||
|
R.string.encryption_enabled,
|
||||||
|
R.string.direct_room_encryption_enabled_tile_description,
|
||||||
|
R.drawable.ic_shield_black
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
resources.getString(R.string.encryption_enabled_tile_description)
|
Triple(R.string.encryption_enabled, R.string.encryption_enabled_tile_description, R.drawable.ic_shield_black)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled)
|
holder.e2eTitleTextView.text = holder.expandView.resources.getString(title)
|
||||||
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
|
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black),
|
ContextCompat.getDrawable(holder.view.context, drawable),
|
||||||
null, null, null
|
null, null, null
|
||||||
)
|
)
|
||||||
holder.e2eTitleDescriptionView.text = description
|
holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(description)
|
||||||
holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem<StatusTileTimelineIte
|
||||||
ShieldUIState.GREEN -> R.drawable.ic_shield_trusted
|
ShieldUIState.GREEN -> R.drawable.ic_shield_trusted
|
||||||
ShieldUIState.BLACK -> R.drawable.ic_shield_black
|
ShieldUIState.BLACK -> R.drawable.ic_shield_black
|
||||||
ShieldUIState.RED -> R.drawable.ic_shield_warning
|
ShieldUIState.RED -> R.drawable.ic_shield_warning
|
||||||
|
ShieldUIState.WAITING -> R.drawable.ic_room_profile_member_list
|
||||||
ShieldUIState.ERROR -> R.drawable.ic_warning_badge
|
ShieldUIState.ERROR -> R.drawable.ic_warning_badge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem<StatusTileTimelineIte
|
||||||
BLACK,
|
BLACK,
|
||||||
RED,
|
RED,
|
||||||
GREEN,
|
GREEN,
|
||||||
|
WAITING,
|
||||||
ERROR
|
ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.features.homeserver
|
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedFactory
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.hilt.EntryPoints
|
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
|
||||||
import im.vector.app.core.di.SingletonEntryPoint
|
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
|
||||||
import im.vector.app.core.platform.EmptyAction
|
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
|
||||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
|
||||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
|
||||||
|
|
||||||
class HomeServerCapabilitiesViewModel @AssistedInject constructor(
|
|
||||||
@Assisted initialState: HomeServerCapabilitiesViewState,
|
|
||||||
private val session: Session,
|
|
||||||
private val rawService: RawService
|
|
||||||
) : VectorViewModel<HomeServerCapabilitiesViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
|
||||||
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory : MavericksAssistedViewModelFactory<HomeServerCapabilitiesViewModel, HomeServerCapabilitiesViewState> {
|
|
||||||
override fun create(initialState: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<HomeServerCapabilitiesViewModel, HomeServerCapabilitiesViewState> by hiltMavericksViewModelFactory() {
|
|
||||||
|
|
||||||
override fun initialState(viewModelContext: ViewModelContext): HomeServerCapabilitiesViewState {
|
|
||||||
val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getSafeActiveSession()
|
|
||||||
return HomeServerCapabilitiesViewState(
|
|
||||||
capabilities = session?.homeServerCapabilitiesService()?.getHomeServerCapabilities() ?: HomeServerCapabilities()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
initAdminE2eByDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initAdminE2eByDefault() {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
val adminE2EByDefault = tryOrNull {
|
|
||||||
rawService.getElementWellknown(session.sessionParams)
|
|
||||||
?.isE2EByDefault()
|
|
||||||
?: true
|
|
||||||
} ?: true
|
|
||||||
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
isE2EByDefault = adminE2EByDefault
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: EmptyAction) {}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.features.homeserver
|
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
|
||||||
|
|
||||||
data class HomeServerCapabilitiesViewState(
|
|
||||||
val capabilities: HomeServerCapabilities = HomeServerCapabilities(),
|
|
||||||
val isE2EByDefault: Boolean = true
|
|
||||||
) : MavericksState
|
|
|
@ -25,6 +25,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.epoxy.noResultItem
|
import im.vector.app.core.epoxy.noResultItem
|
||||||
|
import im.vector.app.core.epoxy.profiles.notifications.textHeaderItem
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
@ -61,6 +62,13 @@ class UserListController @Inject constructor(
|
||||||
val currentState = state ?: return
|
val currentState = state ?: return
|
||||||
val host = this
|
val host = this
|
||||||
|
|
||||||
|
if (currentState.isE2EByDefault && currentState.single3pidSelection && currentState.pendingSelections.isNotEmpty()) {
|
||||||
|
textHeaderItem {
|
||||||
|
id("userListNotificationHeader")
|
||||||
|
textRes(R.string.direct_room_user_list_only_invite_one_email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build generic items
|
// Build generic items
|
||||||
if (currentState.searchTerm.isBlank()) {
|
if (currentState.searchTerm.isBlank()) {
|
||||||
if (currentState.showInviteActions()) {
|
if (currentState.showInviteActions()) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
@ -42,7 +41,6 @@ import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.core.utils.showIdentityServerConsentDialog
|
import im.vector.app.core.utils.showIdentityServerConsentDialog
|
||||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||||
import im.vector.app.databinding.FragmentUserListBinding
|
import im.vector.app.databinding.FragmentUserListBinding
|
||||||
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
|
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -63,7 +61,6 @@ class UserListFragment :
|
||||||
|
|
||||||
private val args: UserListFragmentArgs by args()
|
private val args: UserListFragmentArgs by args()
|
||||||
private val viewModel: UserListViewModel by activityViewModel()
|
private val viewModel: UserListViewModel by activityViewModel()
|
||||||
private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel()
|
|
||||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentUserListBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentUserListBinding {
|
||||||
|
@ -86,7 +83,7 @@ class UserListFragment :
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupSearchView()
|
setupSearchView()
|
||||||
|
|
||||||
homeServerCapabilitiesViewModel.onEach {
|
viewModel.onEach {
|
||||||
views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ data class UserListFragmentArgs(
|
||||||
val submitMenuItemId: Int,
|
val submitMenuItemId: Int,
|
||||||
val excludedUserIds: Set<String>? = null,
|
val excludedUserIds: Set<String>? = null,
|
||||||
val singleSelection: Boolean = false,
|
val singleSelection: Boolean = false,
|
||||||
|
val single3pidSelection: Boolean = false,
|
||||||
val showInviteActions: Boolean = true,
|
val showInviteActions: Boolean = true,
|
||||||
val showContactBookAction: Boolean = true,
|
val showContactBookAction: Boolean = true,
|
||||||
val showToolbar: Boolean = true
|
val showToolbar: Boolean = true
|
||||||
|
|
|
@ -31,6 +31,9 @@ import im.vector.app.core.extensions.toggle
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.discovery.fetchIdentityServerWithTerms
|
import im.vector.app.features.discovery.fetchIdentityServerWithTerms
|
||||||
|
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||||
|
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
@ -41,6 +44,7 @@ import kotlinx.coroutines.flow.sample
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
|
||||||
|
@ -57,6 +61,7 @@ data class ThreePidUser(
|
||||||
class UserListViewModel @AssistedInject constructor(
|
class UserListViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: UserListViewState,
|
@Assisted initialState: UserListViewState,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val rawService: RawService,
|
||||||
private val session: Session
|
private val session: Session
|
||||||
) : VectorViewModel<UserListViewState, UserListAction, UserListViewEvents>(initialState) {
|
) : VectorViewModel<UserListViewState, UserListAction, UserListViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@ -84,6 +89,7 @@ class UserListViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
initAdminE2eByDefault()
|
||||||
observeUsers()
|
observeUsers()
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -93,6 +99,22 @@ class UserListViewModel @AssistedInject constructor(
|
||||||
session.identityService().addListener(identityServerListener)
|
session.identityService().addListener(identityServerListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initAdminE2eByDefault() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val adminE2EByDefault = tryOrNull {
|
||||||
|
rawService.getElementWellknown(session.sessionParams)
|
||||||
|
?.isE2EByDefault()
|
||||||
|
?: true
|
||||||
|
} ?: true
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
isE2EByDefault = adminE2EByDefault
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun cleanISURL(url: String?): String? {
|
private fun cleanISURL(url: String?): String? {
|
||||||
return url?.removePrefix("https://")
|
return url?.removePrefix("https://")
|
||||||
}
|
}
|
||||||
|
@ -258,8 +280,13 @@ class UserListViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state ->
|
private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state ->
|
||||||
val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection)
|
val canSelectUser = !state.isE2EByDefault || state.pendingSelections.isEmpty() || !state.single3pidSelection ||
|
||||||
setState { copy(pendingSelections = selections) }
|
(action.pendingSelection is PendingSelection.UserPendingSelection &&
|
||||||
|
state.pendingSelections.last() is PendingSelection.UserPendingSelection)
|
||||||
|
if (canSelectUser) {
|
||||||
|
val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection)
|
||||||
|
setState { copy(pendingSelections = selections) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingSelection) = withState { state ->
|
private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingSelection) = withState { state ->
|
||||||
|
|
|
@ -32,6 +32,8 @@ data class UserListViewState(
|
||||||
val pendingSelections: Set<PendingSelection> = emptySet(),
|
val pendingSelections: Set<PendingSelection> = emptySet(),
|
||||||
val searchTerm: String = "",
|
val searchTerm: String = "",
|
||||||
val singleSelection: Boolean,
|
val singleSelection: Boolean,
|
||||||
|
val single3pidSelection: Boolean,
|
||||||
|
val isE2EByDefault: Boolean = false,
|
||||||
val configuredIdentityServer: String? = null,
|
val configuredIdentityServer: String? = null,
|
||||||
private val showInviteActions: Boolean,
|
private val showInviteActions: Boolean,
|
||||||
val showContactBookAction: Boolean
|
val showContactBookAction: Boolean
|
||||||
|
@ -40,6 +42,7 @@ data class UserListViewState(
|
||||||
constructor(args: UserListFragmentArgs) : this(
|
constructor(args: UserListFragmentArgs) : this(
|
||||||
excludedUserIds = args.excludedUserIds,
|
excludedUserIds = args.excludedUserIds,
|
||||||
singleSelection = args.singleSelection,
|
singleSelection = args.singleSelection,
|
||||||
|
single3pidSelection = args.single3pidSelection,
|
||||||
showInviteActions = args.showInviteActions,
|
showInviteActions = args.showInviteActions,
|
||||||
showContactBookAction = args.showContactBookAction
|
showContactBookAction = args.showContactBookAction
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue