mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #7189 from vector-im/feature/mna/device-manager-rename-session
[Device management] Rename a session (PSG-747)
This commit is contained in:
commit
223149805b
1
changelog.d/7158.wip
Normal file
1
changelog.d/7158.wip
Normal file
@ -0,0 +1 @@
|
||||
[Device management] Rename a session
|
@ -3295,6 +3295,10 @@
|
||||
<string name="device_manager_session_details_session_id">Session ID</string>
|
||||
<string name="device_manager_session_details_session_last_activity">Last activity</string>
|
||||
<string name="device_manager_session_details_device_ip_address">IP address</string>
|
||||
<string name="device_manager_session_rename">Rename session</string>
|
||||
<string name="device_manager_session_rename_edit_hint">Session name</string>
|
||||
<string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string>
|
||||
<string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string>
|
||||
|
||||
<!-- Note to translators: %s will be replaces with selected space name -->
|
||||
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
|
||||
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="SessionWarningInfoView">
|
||||
<attr name="sessionsWarningInfoDescription" format="string" />
|
||||
<attr name="sessionsWarningInfoHasLearnMore" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@ -325,6 +325,7 @@
|
||||
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity" />
|
||||
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
|
||||
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity" />
|
||||
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
|
@ -91,6 +91,7 @@ import im.vector.app.features.settings.devices.DevicesViewModel
|
||||
import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
|
||||
import im.vector.app.features.settings.devices.v2.rename.RenameSessionViewModel
|
||||
import im.vector.app.features.settings.devtools.AccountDataViewModel
|
||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||
import im.vector.app.features.settings.devtools.KeyRequestListViewModel
|
||||
@ -653,4 +654,9 @@ interface MavericksViewModelModule {
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
||||
fun sessionDetailsViewModelFactory(factory: SessionDetailsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(RenameSessionViewModel::class)
|
||||
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||
import im.vector.app.databinding.ViewSessionWarningInfoBinding
|
||||
|
||||
class SessionWarningInfoView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewSessionWarningInfoBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
var onLearnMoreClickListener: (() -> Unit)? = null
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.SessionWarningInfoView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setDescription(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDescription(typedArray: TypedArray) {
|
||||
val description = typedArray.getString(R.styleable.SessionWarningInfoView_sessionsWarningInfoDescription)
|
||||
val hasLearnMore = typedArray.getBoolean(R.styleable.SessionWarningInfoView_sessionsWarningInfoHasLearnMore, false)
|
||||
if (hasLearnMore) {
|
||||
val learnMore = context.getString(R.string.action_learn_more)
|
||||
val fullDescription = buildString {
|
||||
append(description)
|
||||
append(" ")
|
||||
append(learnMore)
|
||||
}
|
||||
|
||||
binding.sessionWarningInfoDescription.setTextWithColoredPart(
|
||||
fullText = fullDescription,
|
||||
coloredPart = learnMore,
|
||||
underline = false
|
||||
) {
|
||||
onLearnMoreClickListener?.invoke()
|
||||
}
|
||||
} else {
|
||||
binding.sessionWarningInfoDescription.text = description
|
||||
}
|
||||
}
|
||||
}
|
@ -18,11 +18,11 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
@ -31,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.platform.VectorMenuProvider
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||
@ -43,7 +44,8 @@ import javax.inject.Inject
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class SessionOverviewFragment :
|
||||
VectorBaseFragment<FragmentSessionOverviewBinding>() {
|
||||
VectorBaseFragment<FragmentSessionOverviewBinding>(),
|
||||
VectorMenuProvider {
|
||||
|
||||
@Inject lateinit var viewNavigator: SessionOverviewViewNavigator
|
||||
|
||||
@ -103,6 +105,22 @@ class SessionOverviewFragment :
|
||||
views.sessionOverviewInfo.onLearnMoreClickListener = null
|
||||
}
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_session_overview
|
||||
|
||||
override fun handleMenuItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.sessionOverviewRename -> {
|
||||
goToRenameSession()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToRenameSession() = withState(viewModel) { state ->
|
||||
viewNavigator.goToRenameSession(requireContext(), state.deviceId)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
updateToolbar(state.isCurrentSession)
|
||||
updateEntryDetails(state.deviceId)
|
||||
@ -118,7 +136,7 @@ class SessionOverviewFragment :
|
||||
|
||||
private fun updateEntryDetails(deviceId: String) {
|
||||
views.sessionOverviewEntryDetails.setOnClickListener {
|
||||
viewNavigator.navigateToSessionDetails(requireContext(), deviceId)
|
||||
viewNavigator.goToSessionDetails(requireContext(), deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,11 +154,7 @@ class SessionOverviewFragment :
|
||||
)
|
||||
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider)
|
||||
} else {
|
||||
hideSessionInfo()
|
||||
views.sessionOverviewInfo.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideSessionInfo() {
|
||||
views.sessionOverviewInfo.isGone = true
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,16 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
|
||||
import im.vector.app.features.settings.devices.v2.rename.RenameSessionActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class SessionOverviewViewNavigator @Inject constructor() {
|
||||
|
||||
fun navigateToSessionDetails(context: Context, deviceId: String) {
|
||||
fun goToSessionDetails(context: Context, deviceId: String) {
|
||||
context.startActivity(SessionDetailsActivity.newIntent(context, deviceId))
|
||||
}
|
||||
|
||||
fun goToRenameSession(context: Context, deviceId: String) {
|
||||
context.startActivity(RenameSessionActivity.newIntent(context, deviceId))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.settings.devices.v2.rename
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class RenameSessionAction : VectorViewModelAction {
|
||||
object InitWithLastEditedName : RenameSessionAction()
|
||||
object SaveModifications : RenameSessionAction()
|
||||
data class EditLocally(val editedName: String) : RenameSessionAction()
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 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.rename
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
|
||||
/**
|
||||
* Display the screen to rename a Session.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class RenameSessionActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||
|
||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (isFirstCreation()) {
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||
addFragment(
|
||||
container = views.simpleFragmentContainer,
|
||||
fragmentClass = RenameSessionFragment::class.java,
|
||||
params = intent.getParcelableExtra(Mavericks.KEY_ARG)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context, deviceId: String): Intent {
|
||||
return Intent(context, RenameSessionActivity::class.java).apply {
|
||||
putExtra(Mavericks.KEY_ARG, RenameSessionArgs(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.rename
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class RenameSessionArgs(
|
||||
val deviceId: String
|
||||
) : Parcelable
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.showKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentSessionRenameBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Display the screen to rename a Session.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class RenameSessionFragment :
|
||||
VectorBaseFragment<FragmentSessionRenameBinding>() {
|
||||
|
||||
private val viewModel: RenameSessionViewModel by fragmentViewModel()
|
||||
|
||||
@Inject lateinit var viewNavigator: RenameSessionViewNavigator
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionRenameBinding {
|
||||
return FragmentSessionRenameBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
observeViewEvents()
|
||||
initToolbar()
|
||||
initEditText()
|
||||
initSaveButton()
|
||||
initWithLastEditedName()
|
||||
}
|
||||
|
||||
private fun initToolbar() {
|
||||
setupToolbar(views.renameSessionToolbar)
|
||||
.allowBack(useCross = true)
|
||||
}
|
||||
|
||||
private fun initEditText() {
|
||||
views.renameSessionEditText.showKeyboard(andRequestFocus = true)
|
||||
views.renameSessionEditText.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.handle(RenameSessionAction.EditLocally(text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSaveButton() {
|
||||
views.renameSessionSave.debouncedClicks {
|
||||
viewModel.handle(RenameSessionAction.SaveModifications)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWithLastEditedName() {
|
||||
viewModel.handle(RenameSessionAction.InitWithLastEditedName)
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RenameSessionViewEvent.Initialized -> {
|
||||
views.renameSessionEditText.setText(it.deviceName)
|
||||
views.renameSessionEditText.setSelection(views.renameSessionEditText.length())
|
||||
}
|
||||
is RenameSessionViewEvent.SessionRenamed -> {
|
||||
viewNavigator.goBack(requireActivity())
|
||||
}
|
||||
is RenameSessionViewEvent.Failure -> {
|
||||
showFailure(it.throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
views.renameSessionSave.isEnabled = state.editedDeviceName.isNotEmpty()
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.andThen
|
||||
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
class RenameSessionUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
) {
|
||||
|
||||
suspend fun execute(deviceId: String, newName: String): Result<Unit> {
|
||||
return renameDevice(deviceId, newName)
|
||||
.andThen { refreshDevices() }
|
||||
}
|
||||
|
||||
private suspend fun renameDevice(deviceId: String, newName: String) = runCatching {
|
||||
awaitCallback<Unit> { matrixCallback ->
|
||||
activeSessionHolder.getActiveSession()
|
||||
.cryptoService()
|
||||
.setDeviceName(deviceId, newName, matrixCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshDevices() = runCatching { refreshDevicesUseCase.execute() }
|
||||
}
|
@ -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.rename
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class RenameSessionViewEvent : VectorViewEvents {
|
||||
data class Initialized(val deviceName: String) : RenameSessionViewEvent()
|
||||
object SessionRenamed : RenameSessionViewEvent()
|
||||
data class Failure(val throwable: Throwable) : RenameSessionViewEvent()
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
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.settings.devices.v2.overview.GetDeviceFullInfoUseCase
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RenameSessionViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: RenameSessionViewState,
|
||||
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
|
||||
private val renameSessionUseCase: RenameSessionUseCase,
|
||||
) : VectorViewModel<RenameSessionViewState, RenameSessionAction, RenameSessionViewEvent>(initialState) {
|
||||
|
||||
companion object : MavericksViewModelFactory<RenameSessionViewModel, RenameSessionViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<RenameSessionViewModel, RenameSessionViewState> {
|
||||
override fun create(initialState: RenameSessionViewState): RenameSessionViewModel
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
var hasRetrievedOriginalDeviceName = false
|
||||
|
||||
override fun handle(action: RenameSessionAction) {
|
||||
when (action) {
|
||||
is RenameSessionAction.InitWithLastEditedName -> handleInitWithLastEditedName()
|
||||
is RenameSessionAction.EditLocally -> handleEditLocally(action.editedName)
|
||||
is RenameSessionAction.SaveModifications -> handleSaveModifications()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInitWithLastEditedName() = withState { state ->
|
||||
if (hasRetrievedOriginalDeviceName) {
|
||||
postInitEvent()
|
||||
} else {
|
||||
hasRetrievedOriginalDeviceName = true
|
||||
viewModelScope.launch {
|
||||
setStateWithOriginalDeviceName(state.deviceId)
|
||||
postInitEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setStateWithOriginalDeviceName(deviceId: String) {
|
||||
getDeviceFullInfoUseCase.execute(deviceId)
|
||||
.firstOrNull()
|
||||
?.let { deviceFullInfo ->
|
||||
setState { copy(editedDeviceName = deviceFullInfo.deviceInfo.displayName.orEmpty()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun postInitEvent() = withState { state ->
|
||||
_viewEvents.post(RenameSessionViewEvent.Initialized(state.editedDeviceName))
|
||||
}
|
||||
|
||||
private fun handleEditLocally(editedName: String) {
|
||||
setState { copy(editedDeviceName = editedName) }
|
||||
}
|
||||
|
||||
private fun handleSaveModifications() = withState { viewState ->
|
||||
viewModelScope.launch {
|
||||
val result = renameSessionUseCase.execute(
|
||||
deviceId = viewState.deviceId,
|
||||
newName = viewState.editedDeviceName,
|
||||
)
|
||||
val viewEvent = if (result.isSuccess) {
|
||||
RenameSessionViewEvent.SessionRenamed
|
||||
} else {
|
||||
RenameSessionViewEvent.Failure(result.exceptionOrNull() ?: Exception())
|
||||
}
|
||||
_viewEvents.post(viewEvent)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class RenameSessionViewNavigator @Inject constructor() {
|
||||
|
||||
fun goBack(activity: FragmentActivity) {
|
||||
activity.finish()
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
|
||||
data class RenameSessionViewState(
|
||||
val deviceId: String,
|
||||
val editedDeviceName: String = "",
|
||||
) : MavericksState {
|
||||
constructor(args: RenameSessionArgs) : this(
|
||||
deviceId = args.deviceId
|
||||
)
|
||||
}
|
73
vector/src/main/res/layout/fragment_session_rename.xml
Normal file
73
vector/src/main/res/layout/fragment_session_rename.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/renameSessionToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:title="@string/device_manager_session_rename">
|
||||
|
||||
<Button
|
||||
android:id="@+id/renameSessionSave"
|
||||
style="@style/Widget.Vector.Button.Text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/action_save" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/renameSessionInputLayout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="28dp"
|
||||
android:hint="@string/device_manager_session_rename_edit_hint"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/renameSessionEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/renameSessionDescription"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/device_manager_session_rename_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/renameSessionInputLayout" />
|
||||
|
||||
<im.vector.app.features.settings.devices.v2.SessionWarningInfoView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginVertical="@dimen/layout_vertical_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/renameSessionDescription"
|
||||
app:sessionsWarningInfoDescription="@string/device_manager_session_rename_warning"
|
||||
app:sessionsWarningInfoHasLearnMore="false" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
46
vector/src/main/res/layout/view_session_warning_info.xml
Normal file
46
vector/src/main/res/layout/view_session_warning_info.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<View
|
||||
android:id="@+id/sessionWarningInfoBackground"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/rounded_rect_shape_8"
|
||||
android:backgroundTint="?colorSurface"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sessionWarningInfoIcon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginVertical="14dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/sessionWarningInfoDescription"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_info"
|
||||
app:tint="?vctr_content_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sessionWarningInfoDescription"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/sessionWarningInfoIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/sessionWarningInfoBackground"
|
||||
tools:text="Please be aware that session names are also visible to people you communicate with. Learn more" />
|
||||
</merge>
|
11
vector/src/main/res/menu/menu_session_overview.xml
Normal file
11
vector/src/main/res/menu/menu_session_overview.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AlwaysShowAction">
|
||||
|
||||
<item
|
||||
android:id="@+id/sessionOverviewRename"
|
||||
android:title="@string/device_manager_session_rename"
|
||||
app:showAsAction="withText|never" />
|
||||
</menu>
|
@ -18,6 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||
|
||||
import android.content.Intent
|
||||
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
|
||||
import im.vector.app.features.settings.devices.v2.rename.RenameSessionActivity
|
||||
import im.vector.app.test.fakes.FakeContext
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@ -38,6 +39,7 @@ class SessionOverviewViewNavigatorTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkObject(SessionDetailsActivity)
|
||||
mockkObject(RenameSessionActivity)
|
||||
}
|
||||
|
||||
@After
|
||||
@ -52,7 +54,22 @@ class SessionOverviewViewNavigatorTest {
|
||||
context.givenStartActivity(intent)
|
||||
|
||||
// When
|
||||
sessionOverviewViewNavigator.navigateToSessionDetails(context.instance, A_SESSION_ID)
|
||||
sessionOverviewViewNavigator.goToSessionDetails(context.instance, A_SESSION_ID)
|
||||
|
||||
// Then
|
||||
verify {
|
||||
context.instance.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a session id when navigating to rename screen then it starts the correct activity`() {
|
||||
// Given
|
||||
val intent = givenIntentForRenameSession(A_SESSION_ID)
|
||||
context.givenStartActivity(intent)
|
||||
|
||||
// When
|
||||
sessionOverviewViewNavigator.goToRenameSession(context.instance, A_SESSION_ID)
|
||||
|
||||
// Then
|
||||
verify {
|
||||
@ -65,4 +82,10 @@ class SessionOverviewViewNavigatorTest {
|
||||
every { SessionDetailsActivity.newIntent(context.instance, sessionId) } returns intent
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun givenIntentForRenameSession(sessionId: String): Intent {
|
||||
val intent = mockk<Intent>()
|
||||
every { RenameSessionActivity.newIntent(context.instance, sessionId) } returns intent
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
private const val A_DEVICE_ID = "device-id"
|
||||
private const val A_DEVICE_NAME = "device-name"
|
||||
|
||||
class RenameSessionUseCaseTest {
|
||||
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>()
|
||||
|
||||
private val renameSessionUseCase = RenameSessionUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
refreshDevicesUseCase = refreshDevicesUseCase
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given a device id and a new name when no error during rename then the device is renamed with success`() = runTest {
|
||||
// Given
|
||||
fakeActiveSessionHolder.fakeSession.fakeCryptoService.givenSetDeviceNameSucceeds()
|
||||
every { refreshDevicesUseCase.execute() } just runs
|
||||
|
||||
// When
|
||||
val result = renameSessionUseCase.execute(A_DEVICE_ID, A_DEVICE_NAME)
|
||||
|
||||
// Then
|
||||
result.isSuccess shouldBe true
|
||||
verify {
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.cryptoService()
|
||||
.setDeviceName(A_DEVICE_ID, A_DEVICE_NAME, any())
|
||||
refreshDevicesUseCase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a device id and a new name when an error occurs during rename then result is failure`() = runTest {
|
||||
// Given
|
||||
val error = Exception()
|
||||
fakeActiveSessionHolder.fakeSession.fakeCryptoService.givenSetDeviceNameFailsWithError(error)
|
||||
|
||||
// When
|
||||
val result = renameSessionUseCase.execute(A_DEVICE_ID, A_DEVICE_NAME)
|
||||
|
||||
// Then
|
||||
result.isFailure shouldBe true
|
||||
result.exceptionOrNull() shouldBeEqualTo error
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a device id and a new name when an error occurs during devices refresh then result is failure`() = runTest {
|
||||
// Given
|
||||
val error = Exception()
|
||||
fakeActiveSessionHolder.fakeSession.fakeCryptoService.givenSetDeviceNameSucceeds()
|
||||
every { refreshDevicesUseCase.execute() } throws error
|
||||
|
||||
// When
|
||||
val result = renameSessionUseCase.execute(A_DEVICE_ID, A_DEVICE_NAME)
|
||||
|
||||
// Then
|
||||
result.isFailure shouldBe true
|
||||
result.exceptionOrNull() shouldBeEqualTo error
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.overview.GetDeviceFullInfoUseCase
|
||||
import im.vector.app.test.test
|
||||
import im.vector.app.test.testDispatcher
|
||||
import io.mockk.coEvery
|
||||
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
|
||||
|
||||
private const val A_SESSION_ID = "session-id"
|
||||
private const val A_SESSION_NAME = "session-name"
|
||||
private const val AN_EDITED_SESSION_NAME = "edited-session-name"
|
||||
|
||||
class RenameSessionViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher)
|
||||
|
||||
private val args = RenameSessionArgs(
|
||||
deviceId = A_SESSION_ID
|
||||
)
|
||||
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
|
||||
private val renameSessionUseCase = mockk<RenameSessionUseCase>()
|
||||
|
||||
private fun createViewModel() = RenameSessionViewModel(
|
||||
initialState = RenameSessionViewState(args),
|
||||
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
|
||||
renameSessionUseCase = renameSessionUseCase,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given the original device name has not been retrieved when handling init with last edited name action then view state and view events are updated`() {
|
||||
// Given
|
||||
givenSessionWithName(A_SESSION_NAME)
|
||||
val action = RenameSessionAction.InitWithLastEditedName
|
||||
val expectedState = RenameSessionViewState(
|
||||
deviceId = A_SESSION_ID,
|
||||
editedDeviceName = A_SESSION_NAME,
|
||||
)
|
||||
val expectedEvent = RenameSessionViewEvent.Initialized(
|
||||
deviceName = A_SESSION_NAME,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.hasRetrievedOriginalDeviceName = false
|
||||
|
||||
// When
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(action)
|
||||
|
||||
// Then
|
||||
viewModelTest.assertLatestState { state -> state == expectedState }
|
||||
.assertEvent { event -> event == expectedEvent }
|
||||
.finish()
|
||||
verify {
|
||||
getDeviceFullInfoUseCase.execute(A_SESSION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the original device name has been retrieved when handling init with last edited name action then view state and view events are updated`() {
|
||||
// Given
|
||||
val action = RenameSessionAction.InitWithLastEditedName
|
||||
val expectedState = RenameSessionViewState(
|
||||
deviceId = A_SESSION_ID,
|
||||
editedDeviceName = AN_EDITED_SESSION_NAME,
|
||||
)
|
||||
val expectedEvent = RenameSessionViewEvent.Initialized(
|
||||
deviceName = AN_EDITED_SESSION_NAME,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.handle(RenameSessionAction.EditLocally(AN_EDITED_SESSION_NAME))
|
||||
viewModel.hasRetrievedOriginalDeviceName = true
|
||||
|
||||
// When
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(action)
|
||||
|
||||
// Then
|
||||
viewModelTest.assertLatestState { state -> state == expectedState }
|
||||
.assertEvent { event -> event == expectedEvent }
|
||||
.finish()
|
||||
verify(inverse = true) {
|
||||
getDeviceFullInfoUseCase.execute(A_SESSION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a new edited name when handling edit name locally action then view state is updated accordingly`() {
|
||||
// Given
|
||||
val action = RenameSessionAction.EditLocally(AN_EDITED_SESSION_NAME)
|
||||
val expectedState = RenameSessionViewState(
|
||||
deviceId = A_SESSION_ID,
|
||||
editedDeviceName = AN_EDITED_SESSION_NAME,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
// When
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(action)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertLatestState { state -> state == expectedState }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given current edited name when handling save modifications action with success then correct view event is posted`() {
|
||||
// Given
|
||||
coEvery { renameSessionUseCase.execute(A_SESSION_ID, any()) } returns Result.success(Unit)
|
||||
val action = RenameSessionAction.SaveModifications
|
||||
val viewModel = createViewModel()
|
||||
|
||||
// When
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(action)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertEvent { event -> event is RenameSessionViewEvent.SessionRenamed }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given current edited name when handling save modifications action with error then correct view event is posted`() {
|
||||
// Given
|
||||
val error = Exception()
|
||||
coEvery { renameSessionUseCase.execute(A_SESSION_ID, any()) } returns Result.failure(error)
|
||||
val action = RenameSessionAction.SaveModifications
|
||||
val viewModel = createViewModel()
|
||||
|
||||
// When
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(action)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertEvent { event -> event is RenameSessionViewEvent.Failure && event.throwable == error }
|
||||
.finish()
|
||||
}
|
||||
|
||||
private fun givenSessionWithName(sessionName: String) {
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { deviceFullInfo.deviceInfo.displayName } returns sessionName
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.rename
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
|
||||
class RenameSessionViewNavigatorTest {
|
||||
|
||||
private val renameSessionViewNavigator = RenameSessionViewNavigator()
|
||||
|
||||
@Test
|
||||
fun `given an activity when going back then the activity is finished`() {
|
||||
// Given
|
||||
val fragmentActivity = mockk<FragmentActivity>()
|
||||
every { fragmentActivity.finish() } just runs
|
||||
|
||||
// When
|
||||
renameSessionViewNavigator.goBack(fragmentActivity)
|
||||
|
||||
// Then
|
||||
verify {
|
||||
fragmentActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,10 @@
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
@ -50,4 +53,18 @@ class FakeCryptoService(
|
||||
override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData
|
||||
|
||||
override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
|
||||
|
||||
fun givenSetDeviceNameSucceeds() {
|
||||
val matrixCallback = slot<MatrixCallback<Unit>>()
|
||||
every { setDeviceName(any(), any(), capture(matrixCallback)) } answers {
|
||||
thirdArg<MatrixCallback<Unit>>().onSuccess(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun givenSetDeviceNameFailsWithError(error: Exception) {
|
||||
val matrixCallback = slot<MatrixCallback<Unit>>()
|
||||
every { setDeviceName(any(), any(), capture(matrixCallback)) } answers {
|
||||
thirdArg<MatrixCallback<Unit>>().onFailure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user