mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
Verify current session in Session overview screen
This commit is contained in:
parent
fa990351af
commit
584c699409
7 changed files with 256 additions and 26 deletions
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.settings.devices.v2
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import javax.inject.Inject
|
||||
|
||||
class IsCurrentSessionUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
||||
fun execute(deviceId: String): Boolean {
|
||||
val currentDeviceId = activeSessionHolder.getSafeActiveSession()?.sessionParams?.deviceId.orEmpty()
|
||||
return deviceId.isNotEmpty() && deviceId == currentDeviceId
|
||||
}
|
||||
}
|
|
@ -18,4 +18,6 @@ package im.vector.app.features.settings.devices.v2.overview
|
|||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SessionOverviewAction : VectorViewModelAction
|
||||
sealed class SessionOverviewAction : VectorViewModelAction {
|
||||
data class VerifySession(val deviceId: String) : SessionOverviewAction()
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
import javax.inject.Inject
|
||||
|
@ -62,6 +63,7 @@ class SessionOverviewFragment :
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initSessionInfoView()
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
private fun initSessionInfoView() {
|
||||
|
@ -70,6 +72,19 @@ class SessionOverviewFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SessionOverviewViewEvent.SelfVerification -> {
|
||||
navigator.requestSelfSessionVerification(requireActivity())
|
||||
}
|
||||
is SessionOverviewViewEvent.PromptResetSecrets -> {
|
||||
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
cleanUpSessionInfoView()
|
||||
super.onDestroyView()
|
||||
|
@ -81,7 +96,7 @@ class SessionOverviewFragment :
|
|||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
updateToolbar(state.isCurrentSession)
|
||||
updateVerifyButton()
|
||||
updateVerifyButton(state.deviceId)
|
||||
updateEntryDetails(state.deviceId)
|
||||
if (state.deviceInfo is Success) {
|
||||
renderSessionInfo(state.isCurrentSession, state.deviceInfo.invoke())
|
||||
|
@ -97,9 +112,9 @@ class SessionOverviewFragment :
|
|||
?.setTitle(titleResId)
|
||||
}
|
||||
|
||||
private fun updateVerifyButton() {
|
||||
private fun updateVerifyButton(deviceId: String) {
|
||||
views.sessionOverviewInfo.viewVerifyButton.debouncedClicks {
|
||||
// TODO show bottom Sheet verification process
|
||||
viewModel.handle(SessionOverviewAction.VerifySession(deviceId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.settings.devices.v2.overview
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
sealed class SessionOverviewViewEvent : VectorViewEvents {
|
||||
data class SelfVerification(val session: Session) : SessionOverviewViewEvent()
|
||||
object PromptResetSecrets : SessionOverviewViewEvent()
|
||||
}
|
|
@ -21,19 +21,23 @@ import com.airbnb.mvrx.Success
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.settings.devices.v2.IsCurrentSessionUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SessionOverviewViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: SessionOverviewViewState,
|
||||
session: Session,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val isCurrentSessionUseCase: IsCurrentSessionUseCase,
|
||||
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
|
||||
) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||
) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, SessionOverviewViewEvent>(initialState) {
|
||||
|
||||
companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
|
@ -43,14 +47,16 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
val currentDeviceId = session.sessionParams.deviceId.orEmpty()
|
||||
setState {
|
||||
copy(isCurrentSession = deviceId.isNotEmpty() && deviceId == currentDeviceId)
|
||||
copy(isCurrentSession = isCurrentSession(deviceId))
|
||||
}
|
||||
|
||||
observeSessionInfo(initialState.deviceId)
|
||||
}
|
||||
|
||||
private fun isCurrentSession(deviceId: String): Boolean {
|
||||
return isCurrentSessionUseCase.execute(deviceId)
|
||||
}
|
||||
|
||||
private fun observeSessionInfo(deviceId: String) {
|
||||
getDeviceFullInfoUseCase.execute(deviceId)
|
||||
.onEach { setState { copy(deviceInfo = Success(it)) } }
|
||||
|
@ -58,6 +64,27 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun handle(action: SessionOverviewAction) {
|
||||
TODO("Implement when adding the first action")
|
||||
when (action) {
|
||||
is SessionOverviewAction.VerifySession -> handleVerifySessionAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleVerifySessionAction(verifySession: SessionOverviewAction.VerifySession) {
|
||||
if (isCurrentSession(verifySession.deviceId)) {
|
||||
handleVerifyCurrentSession()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleVerifyCurrentSession() {
|
||||
viewModelScope.launch {
|
||||
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
|
||||
if (currentSessionCanBeVerified) {
|
||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||
_viewEvents.post(SessionOverviewViewEvent.SelfVerification(session))
|
||||
}
|
||||
} else {
|
||||
_viewEvents.post(SessionOverviewViewEvent.PromptResetSecrets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.settings.devices.v2
|
||||
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
|
||||
private const val A_SESSION_ID_1 = "session-id-1"
|
||||
private const val A_SESSION_ID_2 = "session-id-2"
|
||||
|
||||
class IsCurrentSessionUseCaseTest {
|
||||
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
|
||||
private val isCurrentSessionUseCase = IsCurrentSessionUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given the session id of the current session when checking if id is current session then result is true`() {
|
||||
// Given
|
||||
val sessionParams = givenIdForCurrentSession(A_SESSION_ID_1)
|
||||
|
||||
// When
|
||||
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_1)
|
||||
|
||||
// Then
|
||||
result shouldBe true
|
||||
verify { sessionParams.deviceId }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a session id different from the current session id when checking if id is current session then result is false`() {
|
||||
// Given
|
||||
val sessionParams = givenIdForCurrentSession(A_SESSION_ID_1)
|
||||
|
||||
// When
|
||||
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_2)
|
||||
|
||||
// Then
|
||||
result shouldBe false
|
||||
verify { sessionParams.deviceId }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no current active session when checking if id is current session then result is false`() {
|
||||
// Given
|
||||
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||
|
||||
// When
|
||||
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_1)
|
||||
|
||||
// Then
|
||||
result shouldBe false
|
||||
}
|
||||
|
||||
private fun givenIdForCurrentSession(deviceId: String): SessionParams {
|
||||
val sessionParams = mockk<SessionParams>()
|
||||
every { sessionParams.deviceId } returns deviceId
|
||||
fakeActiveSessionHolder.fakeSession.givenSessionParams(sessionParams)
|
||||
return sessionParams
|
||||
}
|
||||
}
|
|
@ -19,16 +19,19 @@ package im.vector.app.features.settings.devices.v2.overview
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.features.settings.devices.v2.IsCurrentSessionUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.test
|
||||
import im.vector.app.test.testDispatcher
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
|
||||
private const val A_SESSION_ID = "session-id"
|
||||
|
||||
|
@ -40,24 +43,29 @@ class SessionOverviewViewModelTest {
|
|||
private val args = SessionOverviewArgs(
|
||||
deviceId = A_SESSION_ID
|
||||
)
|
||||
private val fakeSession = FakeSession()
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
private val isCurrentSessionUseCase = mockk<IsCurrentSessionUseCase>()
|
||||
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
|
||||
|
||||
private fun createViewModel() = SessionOverviewViewModel(
|
||||
initialState = SessionOverviewViewState(args),
|
||||
session = fakeSession,
|
||||
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
isCurrentSessionUseCase = isCurrentSessionUseCase,
|
||||
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
|
||||
checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given the viewModel has been initialized then viewState is updated with session info`() {
|
||||
// Given
|
||||
val sessionParams = givenIdForSession(A_SESSION_ID)
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
|
||||
val isCurrentSession = true
|
||||
every { isCurrentSessionUseCase.execute(any()) } returns isCurrentSession
|
||||
val expectedState = SessionOverviewViewState(
|
||||
deviceId = A_SESSION_ID,
|
||||
isCurrentSession = true,
|
||||
isCurrentSession = isCurrentSession,
|
||||
deviceInfo = Success(deviceFullInfo)
|
||||
)
|
||||
|
||||
|
@ -68,14 +76,55 @@ class SessionOverviewViewModelTest {
|
|||
viewModel.test()
|
||||
.assertLatestState { state -> state == expectedState }
|
||||
.finish()
|
||||
verify { sessionParams.deviceId }
|
||||
verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) }
|
||||
verify {
|
||||
isCurrentSessionUseCase.execute(A_SESSION_ID)
|
||||
getDeviceFullInfoUseCase.execute(A_SESSION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenIdForSession(deviceId: String): SessionParams {
|
||||
val sessionParams = mockk<SessionParams>()
|
||||
every { sessionParams.deviceId } returns deviceId
|
||||
fakeSession.givenSessionParams(sessionParams)
|
||||
return sessionParams
|
||||
@Test
|
||||
fun `given current session can be verified when handling verify current session action then self verification event is posted`() {
|
||||
// Given
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
|
||||
every { isCurrentSessionUseCase.execute(any()) } returns true
|
||||
val verifySessionAction = SessionOverviewAction.VerifySession(A_SESSION_ID)
|
||||
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns true
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(verifySessionAction)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertEvent { it is SessionOverviewViewEvent.SelfVerification }
|
||||
.finish()
|
||||
coVerify {
|
||||
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given current session cannot be verified when handling verify current session action then reset secrets event is posted`() {
|
||||
// Given
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
|
||||
every { isCurrentSessionUseCase.execute(any()) } returns true
|
||||
val verifySessionAction = SessionOverviewAction.VerifySession(A_SESSION_ID)
|
||||
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns false
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(verifySessionAction)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertEvent { it is SessionOverviewViewEvent.PromptResetSecrets }
|
||||
.finish()
|
||||
coVerify {
|
||||
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue