mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 10:55:55 +03: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
25 changed files with 1026 additions and 10 deletions
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_id">Session ID</string>
|
||||||
<string name="device_manager_session_details_session_last_activity">Last activity</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_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 -->
|
<!-- 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>
|
<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.overview.SessionOverviewActivity" />
|
||||||
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
|
<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.details.SessionDetailsActivity" />
|
||||||
|
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- 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.details.SessionDetailsViewModel
|
||||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
|
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.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.AccountDataViewModel
|
||||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||||
import im.vector.app.features.settings.devtools.KeyRequestListViewModel
|
import im.vector.app.features.settings.devtools.KeyRequestListViewModel
|
||||||
|
@ -653,4 +654,9 @@ interface MavericksViewModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
||||||
fun sessionDetailsViewModelFactory(factory: SessionDetailsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
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.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
@ -31,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
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.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||||
|
@ -43,7 +44,8 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SessionOverviewFragment :
|
class SessionOverviewFragment :
|
||||||
VectorBaseFragment<FragmentSessionOverviewBinding>() {
|
VectorBaseFragment<FragmentSessionOverviewBinding>(),
|
||||||
|
VectorMenuProvider {
|
||||||
|
|
||||||
@Inject lateinit var viewNavigator: SessionOverviewViewNavigator
|
@Inject lateinit var viewNavigator: SessionOverviewViewNavigator
|
||||||
|
|
||||||
|
@ -103,6 +105,22 @@ class SessionOverviewFragment :
|
||||||
views.sessionOverviewInfo.onLearnMoreClickListener = null
|
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 ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
updateToolbar(state.isCurrentSession)
|
updateToolbar(state.isCurrentSession)
|
||||||
updateEntryDetails(state.deviceId)
|
updateEntryDetails(state.deviceId)
|
||||||
|
@ -118,7 +136,7 @@ class SessionOverviewFragment :
|
||||||
|
|
||||||
private fun updateEntryDetails(deviceId: String) {
|
private fun updateEntryDetails(deviceId: String) {
|
||||||
views.sessionOverviewEntryDetails.setOnClickListener {
|
views.sessionOverviewEntryDetails.setOnClickListener {
|
||||||
viewNavigator.navigateToSessionDetails(requireContext(), deviceId)
|
viewNavigator.goToSessionDetails(requireContext(), deviceId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +154,7 @@ class SessionOverviewFragment :
|
||||||
)
|
)
|
||||||
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider)
|
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider)
|
||||||
} else {
|
} 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 android.content.Context
|
||||||
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
|
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
|
||||||
|
import im.vector.app.features.settings.devices.v2.rename.RenameSessionActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SessionOverviewViewNavigator @Inject constructor() {
|
class SessionOverviewViewNavigator @Inject constructor() {
|
||||||
|
|
||||||
fun navigateToSessionDetails(context: Context, deviceId: String) {
|
fun goToSessionDetails(context: Context, deviceId: String) {
|
||||||
context.startActivity(SessionDetailsActivity.newIntent(context, deviceId))
|
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 android.content.Intent
|
||||||
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
|
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 im.vector.app.test.fakes.FakeContext
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -38,6 +39,7 @@ class SessionOverviewViewNavigatorTest {
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockkObject(SessionDetailsActivity)
|
mockkObject(SessionDetailsActivity)
|
||||||
|
mockkObject(RenameSessionActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -52,7 +54,22 @@ class SessionOverviewViewNavigatorTest {
|
||||||
context.givenStartActivity(intent)
|
context.givenStartActivity(intent)
|
||||||
|
|
||||||
// When
|
// 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
|
// Then
|
||||||
verify {
|
verify {
|
||||||
|
@ -65,4 +82,10 @@ class SessionOverviewViewNavigatorTest {
|
||||||
every { SessionDetailsActivity.newIntent(context.instance, sessionId) } returns intent
|
every { SessionDetailsActivity.newIntent(context.instance, sessionId) } returns intent
|
||||||
return 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
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
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.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
@ -50,4 +53,18 @@ class FakeCryptoService(
|
||||||
override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData
|
override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData
|
||||||
|
|
||||||
override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
|
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 a new issue