diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt index 9b86782c42..fbc4bc5292 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -37,6 +37,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.utils.DimensionConverter +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable import timber.log.Timber /** @@ -87,10 +90,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() return view } + @CallSuper override fun onDestroyView() { super.onDestroyView() unBinder?.unbind() unBinder = null + uiDisposables.clear() + } + + @CallSuper + override fun onDestroy() { + uiDisposables.dispose() + super.onDestroy() } override fun onAttach(context: Context) { @@ -146,4 +157,29 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() protected fun setArguments(args: Parcelable? = null) { arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } } } + + /* ========================================================================================== + * Disposable + * ========================================================================================== */ + + private val uiDisposables = CompositeDisposable() + + protected fun Disposable.disposeOnDestroyView(): Disposable { + uiDisposables.add(this) + return this + } + + /* ========================================================================================== + * ViewEvents + * ========================================================================================== */ + + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + observer(it) + } + .disposeOnDestroyView() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt index 1fcc8d443b..e06df21be5 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt @@ -31,7 +31,11 @@ import com.google.android.material.chip.ChipGroup import com.jakewharton.rxbinding3.widget.textChanges import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R -import im.vector.riotx.core.extensions.* +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.extensions.hideKeyboard +import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.DimensionConverter import kotlinx.android.synthetic.main.fragment_create_direct_room.* @@ -57,8 +61,10 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( setupFilterView() setupAddByMatrixIdView() setupCloseView() - viewModel.selectUserEvent.observeEvent(this) { - updateChipsView(it) + viewModel.observeViewEvents { + when (it) { + is CreateDirectRoomViewEvents.SelectUserAction -> updateChipsView(it) + }.exhaustive } viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) { renderSelectedUsers(it) @@ -132,7 +138,7 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( knownUsersController.setData(it) } - private fun updateChipsView(data: SelectUserAction) { + private fun updateChipsView(data: CreateDirectRoomViewEvents.SelectUserAction) { if (data.isAdded) { addChipToGroup(data.user, chipGroup) } else { diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt new file mode 100644 index 0000000000..168f23c9f7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt @@ -0,0 +1,31 @@ +/* + * 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.riotx.features.createdirect + +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for create direct room screen + */ +sealed class CreateDirectRoomViewEvents : VectorViewEvents { + data class SelectUserAction( + val user: User, + val isAdded: Boolean, + val index: Int + ) : CreateDirectRoomViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 6456acaf22..b115a623a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -18,8 +18,6 @@ package im.vector.riotx.features.createdirect -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import arrow.core.Option import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory @@ -32,10 +30,7 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.rx.rx -import im.vector.riotx.core.extensions.postLiveEvent -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.LiveEvent import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit @@ -43,16 +38,10 @@ import java.util.concurrent.TimeUnit private typealias KnowUsersFilter = String private typealias DirectoryUsersSearch = String -data class SelectUserAction( - val user: User, - val isAdded: Boolean, - val index: Int -) - class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -62,10 +51,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) private val directoryUsersSearch = BehaviorRelay.create() - private val _selectUserEvent = MutableLiveData>() - val selectUserEvent: LiveData> - get() = _selectUserEvent - companion object : MvRxViewModelFactory { @JvmStatic @@ -109,7 +94,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted val index = state.selectedUsers.indexOfFirst { it.userId == action.user.userId } val selectedUsers = state.selectedUsers.minus(action.user) setState { copy(selectedUsers = selectedUsers) } - _selectUserEvent.postLiveEvent(SelectUserAction(action.user, false, index)) + _viewEvents.post(CreateDirectRoomViewEvents.SelectUserAction(action.user, false, index)) } private fun handleSelectUser(action: CreateDirectRoomAction.SelectUser) = withState { state -> @@ -129,7 +114,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted isAddOperation = false } setState { copy(selectedUsers = selectedUsers) } - _selectUserEvent.postLiveEvent(SelectUserAction(action.user, isAddOperation, changeIndex)) + _viewEvents.post(CreateDirectRoomViewEvents.SelectUserAction(action.user, isAddOperation, changeIndex)) } private fun observeDirectoryUsers() { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index b4f9892252..75983a1969 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -23,18 +23,17 @@ import android.widget.TextView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer import androidx.transition.AutoTransition import androidx.transition.TransitionManager import butterknife.BindView import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.commitTransactionNow +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment @@ -56,7 +55,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { val verificationId: String? = null, val roomId: String? = null, // Special mode where UX should show loading wheel until other user sends a request/tx - val waitForIncomingRequest : Boolean = false + val waitForIncomingRequest: Boolean = false ) : Parcelable @Inject @@ -84,17 +83,11 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.requestLiveData.observe(viewLifecycleOwner, Observer { - it.peekContent().let { va -> - when (va) { - is Success -> { - if (va.invoke() is VerificationAction.GotItConclusion) { - dismiss() - } - } - } - } - }) + viewModel.observeViewEvents { + when (it) { + is VerificationBottomSheetViewEvents.Dismiss -> dismiss() + }.exhaustive + } } override fun invalidate() = withState(viewModel) { state -> @@ -250,7 +243,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } - val WAITING_SELF_VERIF_TAG : String = "WAITING_SELF_VERIF_TAG" + const val WAITING_SELF_VERIF_TAG: String = "WAITING_SELF_VERIF_TAG" } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt new file mode 100644 index 0000000000..5509ecbe16 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt @@ -0,0 +1,26 @@ +/* + * 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.riotx.features.crypto.verification + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for the verification bottom sheet + */ +sealed class VerificationBottomSheetViewEvents : VectorViewEvents { + object Dismiss : VerificationBottomSheetViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 5e11570ced..a9f9987c7f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -15,8 +15,6 @@ */ package im.vector.riotx.features.crypto.verification -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -43,9 +41,7 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.extensions.exhaustive -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.LiveEvent data class VerificationBottomSheetViewState( val otherUserMxItem: MatrixItem? = null, @@ -63,14 +59,9 @@ data class VerificationBottomSheetViewState( class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState, @Assisted args: VerificationBottomSheet.VerificationArgs, private val session: Session) - : VectorViewModel(initialState), + : VectorViewModel(initialState), VerificationService.VerificationListener { - // Can be used for several actions, for a one shot result - private val _requestLiveData = MutableLiveData>>() - val requestLiveData: LiveData>> - get() = _requestLiveData - init { session.getVerificationService().addListener(this) @@ -255,7 +246,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini ?.shortCodeDoesNotMatch() } is VerificationAction.GotItConclusion -> { - _requestLiveData.postValue(LiveEvent(Success(action))) + _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) } }.exhaustive } diff --git a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListFragment.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListFragment.kt index c9c98a58ab..e884761cdf 100644 --- a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListFragment.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith -import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.HomeActivitySharedAction @@ -51,8 +51,10 @@ class GroupListFragment @Inject constructor( stateView.contentView = groupListView groupListView.configureWith(groupController) viewModel.subscribe { renderState(it) } - viewModel.openGroupLiveData.observeEvent(this) { - sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) + viewModel.observeViewEvents { + when (it) { + is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) + }.exhaustive } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/DownloadFileState.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewEvents.kt similarity index 60% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/DownloadFileState.kt rename to vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewEvents.kt index 717c59aade..3638934473 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/DownloadFileState.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewEvents.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * 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 + * 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, @@ -14,12 +14,13 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail +package im.vector.riotx.features.grouplist -import java.io.File +import im.vector.riotx.core.platform.VectorViewEvents -data class DownloadFileState( - val mimeType: String, - val file: File?, - val throwable: Throwable? - ) +/** + * Transient events for group list screen + */ +sealed class GroupListViewEvents : VectorViewEvents { + object OpenGroupSummary : GroupListViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt index c31a30b432..f14583c5d5 100644 --- a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt @@ -17,8 +17,6 @@ package im.vector.riotx.features.grouplist -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import arrow.core.Option import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory @@ -32,11 +30,8 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.rx.rx import im.vector.riotx.R -import im.vector.riotx.core.extensions.postLiveEvent -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.LiveEvent import io.reactivex.Observable import io.reactivex.functions.BiFunction @@ -46,7 +41,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private val selectedGroupStore: SelectedGroupDataSource, private val session: Session, private val stringProvider: StringProvider -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -62,9 +57,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro } } - private val _openGroupLiveData = MutableLiveData>() - val openGroupLiveData: LiveData> - get() = _openGroupLiveData + private var currentGroupId = "" init { observeGroupSummaries() @@ -74,10 +67,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private fun observeSelectionState() { selectSubscribe(GroupListViewState::selectedGroup) { groupSummary -> if (groupSummary != null) { - val selectedGroup = _openGroupLiveData.value?.peekContent() // We only want to open group if the updated selectedGroup is a different one. - if (selectedGroup?.groupId != groupSummary.groupId) { - _openGroupLiveData.postLiveEvent(groupSummary) + if (currentGroupId != groupSummary.groupId) { + currentGroupId = groupSummary.groupId + _viewEvents.post(GroupListViewEvents.OpenGroupSummary) } val optionGroup = Option.just(groupSummary) selectedGroupStore.post(optionGroup) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0d6dd292e9..9e4d7d8163 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -89,7 +89,6 @@ import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.hideKeyboard -import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.showKeyboard import im.vector.riotx.core.files.addEntryToDownloadManager @@ -253,12 +252,7 @@ class RoomDetailFragment @Inject constructor( navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) } roomDetailViewModel.subscribe { renderState(it) } - roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) } - roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair -> - val message = getString(pair.first, *pair.second.toTypedArray()) - showSnackWithMessage(message, Snackbar.LENGTH_LONG) - } sharedActionViewModel .observe() .subscribe { @@ -266,34 +260,10 @@ class RoomDetailFragment @Inject constructor( } .disposeOnDestroyView() - roomDetailViewModel.navigateToEvent.observeEvent(this) { - val scrollPosition = timelineEventController.searchPositionOfEvent(it) - if (scrollPosition == null) { - scrollOnHighlightedEventCallback.scheduleScrollTo(it) - } else { - recyclerView.stopScroll() - layoutManager.scrollToPosition(scrollPosition) - } - } - - roomDetailViewModel.fileTooBigEvent.observeEvent(this) { - displayFileTooBigWarning(it) - } - roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) { renderTombstoneEventHandling(it) } - roomDetailViewModel.downloadedFileEvent.observeEvent(this) { downloadFileState -> - val activity = requireActivity() - if (downloadFileState.throwable != null) { - activity.toast(errorFormatter.toHumanReadable(downloadFileState.throwable)) - } else if (downloadFileState.file != null) { - activity.toast(getString(R.string.downloaded_file, downloadFileState.file.path)) - addEntryToDownloadManager(activity, downloadFileState.file, downloadFileState.mimeType) - } - } - roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode -> when (mode) { is SendMode.REGULAR -> renderRegularMode(mode.text) @@ -307,14 +277,17 @@ class RoomDetailFragment @Inject constructor( syncStateView.render(syncState) } - roomDetailViewModel.requestLiveData.observeEvent(this) { - displayRoomDetailActionResult(it) - } - roomDetailViewModel.observeViewEvents { when (it) { is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) + is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) + is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) + is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) + is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) + is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) + is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) + is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) }.exhaustive } } @@ -369,18 +342,38 @@ class RoomDetailFragment @Inject constructor( jumpToReadMarkerView.callback = this } - private fun displayFileTooBigWarning(error: FileTooBigError) { + private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) { + val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId) + if (scrollPosition == null) { + scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId) + } else { + recyclerView.stopScroll() + layoutManager.scrollToPosition(scrollPosition) + } + } + + private fun displayFileTooBigError(action: RoomDetailViewEvents.FileTooBigError) { AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.error_file_too_big, - error.filename, - TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), - TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) + action.filename, + TextUtils.formatFileSize(requireContext(), action.fileSizeInBytes), + TextUtils.formatFileSize(requireContext(), action.homeServerLimitInBytes) )) .setPositiveButton(R.string.ok, null) .show() } + private fun handleDownloadFileState(action: RoomDetailViewEvents.DownloadFileState) { + val activity = requireActivity() + if (action.throwable != null) { + activity.toast(errorFormatter.toHumanReadable(action.throwable)) + } else if (action.file != null) { + activity.toast(getString(R.string.downloaded_file, action.file.path)) + addEntryToDownloadManager(activity, action.file, action.mimeType) + } + } + private fun setupNotificationView() { notificationAreaView.delegate = object : NotificationAreaView.Delegate { override fun onTombstoneEventClicked(tombstoneEvent: Event) { @@ -739,31 +732,31 @@ class RoomDetailFragment @Inject constructor( } } - private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { + private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { when (sendMessageResult) { - is SendMessageResult.MessageSent -> { + is RoomDetailViewEvents.MessageSent -> { updateComposerText("") } - is SendMessageResult.SlashCommandHandled -> { + is RoomDetailViewEvents.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } updateComposerText("") } - is SendMessageResult.SlashCommandError -> { + is RoomDetailViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is SendMessageResult.SlashCommandUnknown -> { + is RoomDetailViewEvents.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is SendMessageResult.SlashCommandResultOk -> { + is RoomDetailViewEvents.SlashCommandResultOk -> { updateComposerText("") } - is SendMessageResult.SlashCommandResultError -> { + is RoomDetailViewEvents.SlashCommandResultError -> { displayCommandError(sendMessageResult.throwable.localizedMessage) } - is SendMessageResult.SlashCommandNotImplemented -> { + is RoomDetailViewEvents.SlashCommandNotImplemented -> { displayCommandError(getString(R.string.not_implemented)) } - } + } // .exhaustive lockSendButton = false } @@ -793,84 +786,81 @@ class RoomDetailFragment @Inject constructor( .show() } - private fun displayRoomDetailActionResult(result: Async) { - when (result) { - is Fail -> { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(result.error)) - .setPositiveButton(R.string.ok, null) - .show() - } - is Success -> { - when (val data = result.invoke()) { - is RoomDetailAction.ReportContent -> { - when { - data.spam -> { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.content_reported_as_spam_title) - .setMessage(R.string.content_reported_as_spam_content) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> - roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) - } - .show() - .withColoredButton(DialogInterface.BUTTON_NEGATIVE) - } - data.inappropriate -> { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.content_reported_as_inappropriate_title) - .setMessage(R.string.content_reported_as_inappropriate_content) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> - roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) - } - .show() - .withColoredButton(DialogInterface.BUTTON_NEGATIVE) - } - else -> { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.content_reported_title) - .setMessage(R.string.content_reported_content) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> - roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) - } - .show() - .withColoredButton(DialogInterface.BUTTON_NEGATIVE) - } - } + private fun displayRoomDetailActionFailure(result: RoomDetailViewEvents.ActionFailure) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(result.throwable)) + .setPositiveButton(R.string.ok, null) + .show() + } + + private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) { + when (val data = result.action) { + is RoomDetailAction.ReportContent -> { + when { + data.spam -> { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.content_reported_as_spam_title) + .setMessage(R.string.content_reported_as_spam_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) + } + .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } - is RoomDetailAction.RequestVerification -> { - Timber.v("## SAS RequestVerification action") - VerificationBottomSheet.withArgs( - roomDetailArgs.roomId, - data.userId - ).show(parentFragmentManager, "REQ") + data.inappropriate -> { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.content_reported_as_inappropriate_title) + .setMessage(R.string.content_reported_as_inappropriate_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) + } + .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } - is RoomDetailAction.AcceptVerificationRequest -> { - Timber.v("## SAS AcceptVerificationRequest action") - VerificationBottomSheet.withArgs( - roomDetailArgs.roomId, - data.otherUserId, - data.transactionId - ).show(parentFragmentManager, "REQ") - } - is RoomDetailAction.ResumeVerification -> { - val otherUserId = data.otherUserId ?: return - VerificationBottomSheet().apply { - arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( - otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) - } - }.show(parentFragmentManager, "REQ") + else -> { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.content_reported_title) + .setMessage(R.string.content_reported_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) + } + .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } } } + is RoomDetailAction.RequestVerification -> { + Timber.v("## SAS RequestVerification action") + VerificationBottomSheet.withArgs( + roomDetailArgs.roomId, + data.userId + ).show(parentFragmentManager, "REQ") + } + is RoomDetailAction.AcceptVerificationRequest -> { + Timber.v("## SAS AcceptVerificationRequest action") + VerificationBottomSheet.withArgs( + roomDetailArgs.roomId, + data.otherUserId, + data.transactionId + ).show(parentFragmentManager, "REQ") + } + is RoomDetailAction.ResumeVerification -> { + val otherUserId = data.otherUserId ?: return + VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( + otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) + } + }.show(parentFragmentManager, "REQ") + } } } -// TimelineEventController.Callback ************************************************************ + // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String): Boolean { permalinkHandler diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index 6470e0c338..fcbe7f37c0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -16,7 +16,10 @@ package im.vector.riotx.features.home.room.detail +import androidx.annotation.StringRes import im.vector.riotx.core.platform.VectorViewEvents +import im.vector.riotx.features.command.Command +import java.io.File /** * Transient events for RoomDetail @@ -24,4 +27,34 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class RoomDetailViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomDetailViewEvents() data class OnNewTimelineEvents(val eventIds: List) : RoomDetailViewEvents() + + data class ActionSuccess(val action: RoomDetailAction) : RoomDetailViewEvents() + data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents() + + data class ShowMessage(val message: String) : RoomDetailViewEvents() + + data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents() + + data class FileTooBigError( + val filename: String, + val fileSizeInBytes: Long, + val homeServerLimitInBytes: Long + ) : RoomDetailViewEvents() + + data class DownloadFileState( + val mimeType: String, + val file: File?, + val throwable: Throwable? + ) : RoomDetailViewEvents() + + abstract class SendMessageResult : RoomDetailViewEvents() + + object MessageSent : SendMessageResult() + class SlashCommandError(val command: Command) : SendMessageResult() + class SlashCommandUnknown(val command: String) : SendMessageResult() + data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() + object SlashCommandResultOk : SendMessageResult() + class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() + // TODO Remove + object SlashCommandNotImplemented : SendMessageResult() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2bee55bce3..710c70a948 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -18,10 +18,6 @@ package im.vector.riotx.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success @@ -32,6 +28,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType @@ -61,17 +58,14 @@ import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.extensions.exhaustive -import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.UserPreferencesProvider -import im.vector.riotx.core.utils.LiveEvent -import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand -import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.crypto.verification.supportedVerificationMethods +import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.settings.VectorPreferences @@ -116,11 +110,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro var timeline = room.createTimeline(eventId, timelineSettings) private set - // Can be used for several actions, for a one shot result - private val _requestLiveData = MutableLiveData>>() - val requestLiveData: LiveData>> - get() = _requestLiveData - // Slot to keep a pending action during permission request var pendingAction: RoomDetailAction? = null // Slot to keep a pending uri during permission request @@ -320,27 +309,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } - // TODO Cleanup this and use ViewEvents - private val _nonBlockingPopAlert = MutableLiveData>>>() - val nonBlockingPopAlert: LiveData>>> - get() = _nonBlockingPopAlert - - private val _sendMessageResultLiveData = MutableLiveData>() - val sendMessageResultLiveData: LiveData> - get() = _sendMessageResultLiveData - - private val _navigateToEvent = MutableLiveData>() - val navigateToEvent: LiveData> - get() = _navigateToEvent - - private val _fileTooBigEvent = MutableLiveData>() - val fileTooBigEvent: LiveData> - get() = _fileTooBigEvent - - private val _downloadedFileEvent = MutableLiveData>() - val downloadedFileEvent: LiveData> - get() = _downloadedFileEvent - fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) { R.id.clear_message_queue -> /* For now always disable on production, worker cancellation is not working properly */ @@ -360,17 +328,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent) + _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } is ParsedCommand.ErrorSyntax -> { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)) + _viewEvents.post(RoomDetailViewEvents.SlashCommandError(slashCommandResult.command)) } is ParsedCommand.ErrorEmptySlashCommand -> { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown("/")) + _viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown("/")) } is ParsedCommand.ErrorUnknownSlashCommand -> { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)) + _viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) } is ParsedCommand.Invite -> { handleInviteSlashCommand(slashCommandResult) @@ -378,55 +346,55 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } is ParsedCommand.SetUserPowerLevel -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.ClearScalarToken -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.SetMarkdown -> { vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled( + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled( if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) popDraft() } is ParsedCommand.UnbanUser -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.BanUser -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.KickUser -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.JoinRoom -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.PartRoom -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.SendRainbow -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) } - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.SendRainbowEmote -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) } - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.SendSpoiler -> { @@ -434,7 +402,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", "${slashCommandResult.message}" ) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.SendShrug -> { @@ -446,12 +414,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } room.sendTextMessage(sequence) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.VerifyUser -> { session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } is ParsedCommand.ChangeTopic -> { @@ -460,7 +428,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } is ParsedCommand.ChangeDisplayName -> { // TODO - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) + _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } }.exhaustive } @@ -487,7 +455,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro Timber.w("Same message content, do not send edition") } } - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent) + _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } is SendMode.QUOTE -> { @@ -510,13 +478,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { room.sendFormattedTextMessage(finalText, htmlText) } - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent) + _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent) + _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } } @@ -549,29 +517,29 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) room.updateTopic(changeTopic.topic, object : MatrixCallback { override fun onSuccess(data: Unit) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk) + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultOk) } override fun onFailure(failure: Throwable) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure)) + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) } }) } private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) room.invite(invite.userId, invite.reason, object : MatrixCallback { override fun onSuccess(data: Unit) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk) + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultOk) } override fun onFailure(failure: Throwable) { - _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure)) + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) } }) } @@ -608,8 +576,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { null -> room.sendMedias(attachments) - else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name - ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize))) + else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError( + tooBigFile.name ?: tooBigFile.path, + tooBigFile.size, + maxUploadFileSize + )) } } } @@ -721,7 +692,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(), object : MatrixCallback { override fun onSuccess(data: File) { - _downloadedFileEvent.postLiveEvent(DownloadFileState( + _viewEvents.post(RoomDetailViewEvents.DownloadFileState( action.messageFileContent.getMimeType(), data, null @@ -729,7 +700,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } override fun onFailure(failure: Throwable) { - _downloadedFileEvent.postLiveEvent(DownloadFileState( + _viewEvents.post(RoomDetailViewEvents.DownloadFileState( action.messageFileContent.getMimeType(), null, failure @@ -750,7 +721,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro if (action.highlight) { setState { copy(highlightedEventId = correctedEventId) } } - _navigateToEvent.postLiveEvent(correctedEventId) + _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(correctedEventId)) } private fun handleResendEvent(action: RoomDetailAction.ResendMessage) { @@ -821,11 +792,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleReportContent(action: RoomDetailAction.ReportContent) { room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback { override fun onSuccess(data: Unit) { - _requestLiveData.postValue(LiveEvent(Success(action))) + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } override fun onFailure(failure: Throwable) { - _requestLiveData.postValue(LiveEvent(Fail(failure))) + _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) } }) } @@ -837,11 +808,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro session.ignoreUserIds(listOf(action.userId), object : MatrixCallback { override fun onSuccess(data: Unit) { - _requestLiveData.postValue(LiveEvent(Success(action))) + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } override fun onFailure(failure: Throwable) { - _requestLiveData.postValue(LiveEvent(Fail(failure))) + _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) } }) } @@ -853,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro action.otherUserId, room.roomId, action.transactionId)) { - _requestLiveData.postValue(LiveEvent(Success(action))) + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } else { // TODO } @@ -869,7 +840,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) { if (action.userId == session.myUserId) return - _requestLiveData.postValue(LiveEvent(Success(action))) + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) { @@ -877,9 +848,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro session.getVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let { if (it.handledByOtherSession) return if (!it.isFinished) { - _requestLiveData.postValue(LiveEvent(Success(action.copy( + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action.copy( otherUserId = it.otherUserId - )))) + ))) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/SendMessageResult.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/SendMessageResult.kt deleted file mode 100644 index 9f8d7304f5..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/SendMessageResult.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2019 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.riotx.features.home.room.detail - -import androidx.annotation.StringRes -import im.vector.riotx.features.command.Command - -sealed class SendMessageResult { - object MessageSent : SendMessageResult() - class SlashCommandError(val command: Command) : SendMessageResult() - class SlashCommandUnknown(val command: String) : SendMessageResult() - data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() - object SlashCommandResultOk : SendMessageResult() - class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() - // TODO Remove - object SlashCommandNotImplemented : SendMessageResult() -} diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index 580844ddfa..0cb2a7feca 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -28,7 +28,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith -import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseFragment import io.reactivex.rxkotlin.subscribeBy import kotlinx.android.synthetic.main.fragment_public_rooms.* @@ -75,13 +75,20 @@ class PublicRoomsFragment @Inject constructor( sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) } - // TODO remove this, replace by ViewEvents - viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable -> - Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) - .show() + viewModel.observeViewEvents { + handleViewEvents(it) } } + private fun handleViewEvents(viewEvents: RoomDirectoryViewEvents) { + when (viewEvents) { + is RoomDirectoryViewEvents.Failure -> { + Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(viewEvents.throwable), Snackbar.LENGTH_SHORT) + .show() + } + }.exhaustive + } + override fun onDestroyView() { publicRoomsController.callback = null publicRoomsList.cleanup() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewEvents.kt similarity index 57% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt rename to vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewEvents.kt index 0f9bfebb47..499e8d390d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewEvents.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * 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 + * 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, @@ -14,10 +14,13 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail +package im.vector.riotx.features.roomdirectory -data class FileTooBigError( - val filename: String, - val fileSizeInBytes: Long, - val homeServerLimitInBytes: Long -) +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for room directory screen + */ +sealed class RoomDirectoryViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : RoomDirectoryViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index bab07795e9..9f81e8b076 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -16,9 +16,13 @@ package im.vector.riotx.features.roomdirectory -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.* +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.ViewModelContext +import com.airbnb.mvrx.appendAt import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -32,17 +36,14 @@ import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryD import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.rx.rx -import im.vector.riotx.core.extensions.postLiveEvent -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.LiveEvent import timber.log.Timber private const val PUBLIC_ROOMS_LIMIT = 20 class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -58,10 +59,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: } } - private val _joinRoomErrorLiveData = MutableLiveData>() - val joinRoomErrorLiveData: LiveData> - get() = _joinRoomErrorLiveData - private var since: String? = null private var currentTask: Cancelable? = null @@ -109,9 +106,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: override fun handle(action: RoomDirectoryAction) { when (action) { is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action) - is RoomDirectoryAction.FilterWith -> filterWith(action) - RoomDirectoryAction.LoadMore -> loadMore() - is RoomDirectoryAction.JoinRoom -> joinRoom(action) + is RoomDirectoryAction.FilterWith -> filterWith(action) + RoomDirectoryAction.LoadMore -> loadMore() + is RoomDirectoryAction.JoinRoom -> joinRoom(action) } } @@ -227,7 +224,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: override fun onFailure(failure: Throwable) { // Notify the user - _joinRoomErrorLiveData.postLiveEvent(failure) + _viewEvents.post(RoomDirectoryViewEvents.Failure(failure)) setState { copy( diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt index 5b890d1ef6..e352683841 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt @@ -20,8 +20,7 @@ package im.vector.riotx.features.roommemberprofile import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomMemberProfileAction : VectorViewModelAction { - - object RetryFetchingInfo: RoomMemberProfileAction() - object IgnoreUser: RoomMemberProfileAction() - data class VerifyUser(val userId: String? = null, val roomId: String? = null, val canCrossSign: Boolean? = true): RoomMemberProfileAction() + object RetryFetchingInfo : RoomMemberProfileAction() + object IgnoreUser : RoomMemberProfileAction() + object VerifyUser : RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index e9ecbf78e3..89b37400f1 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -35,7 +35,6 @@ import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.exhaustive -import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment @@ -94,33 +93,27 @@ class RoomMemberProfileFragment @Inject constructor( is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit + is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it) }.exhaustive } - viewModel.actionResultLiveData.observeEvent(this) { async -> - when (async) { - is Success -> { - when (val action = async.invoke()) { - is RoomMemberProfileAction.VerifyUser -> { - if (action.canCrossSign == true) { - VerificationBottomSheet - .withArgs(roomId = null, otherUserId = action.userId!!) - .show(parentFragmentManager, "VERIF") - } else { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.dialog_title_warning) - .setMessage(R.string.verify_cannot_cross_sign) - .setPositiveButton(R.string.verification_profile_verify) { _, _ -> - VerificationBottomSheet - .withArgs(roomId = null, otherUserId = action.userId!!) - .show(parentFragmentManager, "VERIF") - } - .setNegativeButton(R.string.cancel, null) - .show() - } - } + } + + private fun handleStartVerification(startVerification: RoomMemberProfileViewEvents.StartVerification) { + if (startVerification.canCrossSign) { + VerificationBottomSheet + .withArgs(roomId = null, otherUserId = startVerification.userId) + .show(parentFragmentManager, "VERIF") + } else { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.dialog_title_warning) + .setMessage(R.string.verify_cannot_cross_sign) + .setPositiveButton(R.string.verification_profile_verify) { _, _ -> + VerificationBottomSheet + .withArgs(roomId = null, otherUserId = startVerification.userId) + .show(parentFragmentManager, "VERIF") } - } - } + .setNegativeButton(R.string.cancel, null) + .show() } } @@ -197,7 +190,7 @@ class RoomMemberProfileFragment @Inject constructor( } override fun onTapVerify() { - viewModel.handle(RoomMemberProfileAction.VerifyUser()) + viewModel.handle(RoomMemberProfileAction.VerifyUser) } override fun onShowDeviceList() = withState(viewModel) { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt index 4647c124a6..5b7f5bf54c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -26,4 +26,9 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() + + data class StartVerification( + val userId: String, + val canCrossSign: Boolean + ) : RoomMemberProfileViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index 2af7a6ec35..e3dd53104c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -17,10 +17,7 @@ package im.vector.riotx.features.roommemberprofile -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success @@ -49,7 +46,6 @@ import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.LiveEvent import io.reactivex.Observable import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers @@ -75,10 +71,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - private val _actionResultLiveData = MutableLiveData>>() - val actionResultLiveData: LiveData>> - get() = _actionResultLiveData - private val room = if (initialState.roomId != null) { session.getRoom(initialState.roomId) } else { @@ -145,23 +137,19 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v when (action) { is RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo() is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction() - is RoomMemberProfileAction.VerifyUser -> prepareVerification(action) + is RoomMemberProfileAction.VerifyUser -> prepareVerification() } } - private fun prepareVerification(action: RoomMemberProfileAction.VerifyUser) = withState { state -> + private fun prepareVerification() = withState { state -> // Sanity if (state.isRoomEncrypted) { if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted() == false) { // ok, let's find or create the DM room - _actionResultLiveData.postValue( - LiveEvent(Success( - action.copy( - userId = state.userId, - canCrossSign = session.getCrossSigningService().canCrossSign() - ) - )) - ) + _viewEvents.post(RoomMemberProfileViewEvents.StartVerification( + userId = state.userId, + canCrossSign = session.getCrossSigningService().canCrossSign() + )) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListAction.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListAction.kt new file mode 100644 index 0000000000..58a486c1b6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListAction.kt @@ -0,0 +1,27 @@ +/* + * 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.riotx.features.roommemberprofile.devices + +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class DeviceListAction : VectorViewModelAction { + data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction() + object DeselectDevice : DeviceListAction() + + data class ManuallyVerify(val deviceId: String) : DeviceListAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt index dc8ed66fb8..591b014c72 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -21,15 +21,13 @@ import android.os.Bundle import android.view.KeyEvent import androidx.fragment.app.Fragment import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.commitTransaction -import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.riotx.features.crypto.verification.VerificationAction import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import javax.inject.Inject import kotlin.reflect.KClass @@ -48,20 +46,16 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - viewModel.requestLiveData.observeEvent(this) { async -> - when (async) { - is Success -> { - when (val action = async.invoke()) { - is VerificationAction.StartSASVerification -> { - VerificationBottomSheet.withArgs( - roomId = null, - otherUserId = action.otherUserId, - transactionId = action.pendingRequestTransactionId - ).show(requireActivity().supportFragmentManager, "REQPOP") - } - } + viewModel.observeViewEvents { + when (it) { + is DeviceListBottomSheetViewEvents.Verify -> { + VerificationBottomSheet.withArgs( + roomId = null, + otherUserId = it.userId, + transactionId = it.txID + ).show(requireActivity().supportFragmentManager, "REQPOP") } - } + }.exhaustive } } @@ -69,7 +63,7 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() { withState(viewModel) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (it.selectedDevice != null) { - viewModel.selectDevice(null) + viewModel.handle(DeviceListAction.DeselectDevice) return@withState true } else { return@withState false diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt new file mode 100644 index 0000000000..39758671c8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt @@ -0,0 +1,26 @@ +/* + * 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.riotx.features.roommemberprofile.devices + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for device list screen + */ +sealed class DeviceListBottomSheetViewEvents : VectorViewEvents { + data class Verify(val userId: String, val txID: String) : DeviceListBottomSheetViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 667c3b6cdc..b12ed58a73 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -16,14 +16,11 @@ */ package im.vector.riotx.features.roommemberprofile.devices -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -35,12 +32,8 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.rx.rx import im.vector.riotx.core.di.HasScreenInjector -import im.vector.riotx.core.platform.EmptyAction -import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.LiveEvent -import im.vector.riotx.features.crypto.verification.VerificationAction data class DeviceListViewState( val userItem: MatrixItem? = null, @@ -52,14 +45,8 @@ data class DeviceListViewState( class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted private val initialState: DeviceListViewState, @Assisted private val userId: String, - private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { - - // Can be used for several actions, for a one shot result - private val _requestLiveData = MutableLiveData>>() - val requestLiveData: LiveData>> - get() = _requestLiveData + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -67,7 +54,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } init { - session.rx().liveUserCryptoDevices(userId) .execute { copy(cryptoDevices = it).also { @@ -81,6 +67,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } } + override fun handle(action: DeviceListAction) { + when (action) { + is DeviceListAction.SelectDevice -> selectDevice(action) + is DeviceListAction.DeselectDevice -> deselectDevice() + is DeviceListAction.ManuallyVerify -> manuallyVerify(action) + }.exhaustive + } + private fun refreshSelectedId() = withState { state -> if (state.selectedDevice != null) { state.cryptoDevices.invoke()?.firstOrNull { state.selectedDevice.deviceId == it.deviceId }?.let { @@ -93,19 +87,23 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } } - fun selectDevice(device: CryptoDeviceInfo?) { + private fun selectDevice(action: DeviceListAction.SelectDevice) { setState { - copy(selectedDevice = device) + copy(selectedDevice = action.device) } } - fun manuallyVerify(device: CryptoDeviceInfo) { - session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId, null)?.let { txID -> - _requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID)))) + private fun deselectDevice() { + setState { + copy(selectedDevice = null) } } - override fun handle(action: EmptyAction) {} + private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) { + session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, action.deviceId, null)?.let { txID -> + _viewEvents.post(DeviceListBottomSheetViewEvents.Verify(userId, txID)) + } + } companion object : MvRxViewModelFactory { @JvmStatic diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListFragment.kt index c598d051f7..93c51b2008 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListFragment.kt @@ -62,6 +62,6 @@ class DeviceListFragment @Inject constructor( } override fun onDeviceSelected(device: CryptoDeviceInfo) { - viewModel.selectDevice(device) + viewModel.handle(DeviceListAction.SelectDevice(device)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt index 6c9de742bc..d955e4c9dc 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt @@ -62,6 +62,6 @@ class DeviceTrustInfoActionFragment @Inject constructor( } override fun onVerifyManually(device: CryptoDeviceInfo) { - viewModel.manuallyVerify(device) + viewModel.handle(DeviceListAction.ManuallyVerify(device.deviceId)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt index fef10019a5..ec56929002 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -18,16 +18,13 @@ package im.vector.riotx.features.settings.crosssigning import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.riotx.R import im.vector.riotx.core.dialogs.PromptPasswordDialog import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith -import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import kotlinx.android.synthetic.main.fragment_generic_recycler.* @@ -44,23 +41,20 @@ class CrossSigningSettingsFragment @Inject constructor( override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - viewModel.requestLiveData.observeEvent(this) { + viewModel.observeViewEvents { when (it) { - is Fail -> { + is CrossSigningSettingsViewEvents.Failure -> { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_error) - .setMessage(it.error.message) + .setMessage(errorFormatter.toHumanReadable(it.throwable)) .setPositiveButton(R.string.ok, null) .show() + Unit } - is Success -> { - when (val action = it.invoke()) { - is CrossSigningAction.RequestPasswordAuth -> { - requestPassword(action.sessionId) - } - } + is CrossSigningSettingsViewEvents.RequestPassword -> { + requestPassword() } - } + }.exhaustive } } @@ -89,18 +83,14 @@ class CrossSigningSettingsFragment @Inject constructor( super.onDestroyView() } - private fun requestPassword(sessionId: String) { + private fun requestPassword() { PromptPasswordDialog().show(requireActivity()) { password -> - // TODO sessionId should never get out the ViewModel - viewModel.handle(CrossSigningAction.InitializeCrossSigning(UserPasswordAuth( - session = sessionId, - password = password - ))) + viewModel.handle(CrossSigningAction.PasswordEntered(password)) } } override fun onInitializeCrossSigningKeys() { - viewModel.handle(CrossSigningAction.InitializeCrossSigning()) + viewModel.handle(CrossSigningAction.InitializeCrossSigning) } override fun onResetCrossSigningKeys() { @@ -108,7 +98,7 @@ class CrossSigningSettingsFragment @Inject constructor( .setTitle(R.string.dialog_title_confirmation) .setMessage(R.string.are_you_sure) .setPositiveButton(R.string.ok) { _, _ -> - viewModel.handle(CrossSigningAction.InitializeCrossSigning()) + viewModel.handle(CrossSigningAction.InitializeCrossSigning) } .show() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt new file mode 100644 index 0000000000..b03707b363 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -0,0 +1,28 @@ +/* + * 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.riotx.features.settings.crosssigning + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for cross signing settings screen + */ +sealed class CrossSigningSettingsViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() + + object RequestPassword : CrossSigningSettingsViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index e95bf50de9..2ad2e8c1ca 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -15,14 +15,9 @@ */ package im.vector.riotx.features.settings.crosssigning -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -36,11 +31,9 @@ import im.vector.matrix.android.internal.crypto.crosssigning.isVerified import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.rx.rx -import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModelAction -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.LiveEvent data class CrossSigningSettingsViewState( val crossSigningInfo: MXCrossSigningInfo? = null, @@ -51,19 +44,13 @@ data class CrossSigningSettingsViewState( ) : MvRxState sealed class CrossSigningAction : VectorViewModelAction { - data class InitializeCrossSigning(val auth: UserPasswordAuth? = null) : CrossSigningAction() - data class RequestPasswordAuth(val sessionId: String) : CrossSigningAction() + object InitializeCrossSigning : CrossSigningAction() + data class PasswordEntered(val password: String) : CrossSigningAction() } class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted private val initialState: CrossSigningSettingsViewState, - private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { - - // Can be used for several actions, for a one shot result - private val _requestLiveData = MutableLiveData>>() - val requestLiveData: LiveData>> - get() = _requestLiveData + : VectorViewModel(initialState) { init { session.rx().liveCrossSigningInfo(session.myUserId) @@ -81,6 +68,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat } } + // Storage when password is required + private var _pendingSession: String? = null + @AssistedInject.Factory interface Factory { fun create(initialState: CrossSigningSettingsViewState): CrossSigningSettingsViewModel @@ -89,26 +79,37 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat override fun handle(action: CrossSigningAction) { when (action) { is CrossSigningAction.InitializeCrossSigning -> { - initializeCrossSigning(action.auth?.copy(user = session.myUserId)) + initializeCrossSigning(null) } - } + is CrossSigningAction.PasswordEntered -> { + initializeCrossSigning(UserPasswordAuth( + session = _pendingSession, + user = session.myUserId, + password = action.password + )) + } + }.exhaustive } private fun initializeCrossSigning(auth: UserPasswordAuth?) { + _pendingSession = null + setState { copy(isUploadingKeys = true) } session.getCrossSigningService().initializeCrossSigning(auth, object : MatrixCallback { override fun onSuccess(data: Unit) { + _pendingSession = null + setState { copy(isUploadingKeys = false) } } override fun onFailure(failure: Throwable) { - if (failure is Failure.OtherServerError - && failure.httpCode == 401 - ) { + _pendingSession = null + + if (failure is Failure.OtherServerError && failure.httpCode == 401) { try { MoshiProvider.providesMoshi() .adapter(RegistrationFlowResponse::class.java) @@ -118,23 +119,23 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat }?.let { flowResponse -> // Retry with authentication if (flowResponse.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) { - _requestLiveData.postValue(LiveEvent(Success(CrossSigningAction.RequestPasswordAuth(flowResponse.session ?: "")))) + _pendingSession = flowResponse.session ?: "" + _viewEvents.post(CrossSigningSettingsViewEvents.RequestPassword) return } else { - _requestLiveData.postValue(LiveEvent(Fail(Throwable("You cannot do that from mobile")))) // can't do this from here + _viewEvents.post(CrossSigningSettingsViewEvents.Failure(Throwable("You cannot do that from mobile"))) + + setState { + copy(isUploadingKeys = false) + } return } } } - when (failure) { - is Failure.ServerError -> { - _requestLiveData.postValue(LiveEvent(Fail(Throwable(failure.error.message)))) - } - else -> { - _requestLiveData.postValue(LiveEvent(Fail(failure))) - } - } + + _viewEvents.post(CrossSigningSettingsViewEvents.Failure(failure)) + setState { copy(isUploadingKeys = false) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt index 29fbb9ca46..e4b1b98cc8 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt @@ -16,14 +16,14 @@ package im.vector.riotx.features.settings.devices -import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.riotx.core.platform.VectorViewModelAction sealed class DevicesAction : VectorViewModelAction { object Retry : DevicesAction() data class Delete(val deviceId: String) : DevicesAction() data class Password(val password: String) : DevicesAction() - data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction() - data class PromptRename(val deviceId: String, val deviceInfo: DeviceInfo? = null) : DevicesAction() - data class VerifyMyDevice(val deviceId: String, val userId: String? = null, val transactionId: String? = null) : DevicesAction() + data class Rename(val deviceId: String, val newName: String) : DevicesAction() + + data class PromptRename(val deviceId: String) : DevicesAction() + data class VerifyMyDevice(val deviceId: String) : DevicesAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt index b42738439a..075eb2050e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.settings.devices +import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.riotx.core.platform.VectorViewEvents /** @@ -25,4 +26,13 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class DevicesViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : DevicesViewEvents() data class Failure(val throwable: Throwable) : DevicesViewEvents() + + object RequestPassword : DevicesViewEvents() + + data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvents() + + data class ShowVerifyDevice( + val userId: String, + val transactionId: String? + ) : DevicesViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index 419b6a5492..333249f3de 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -16,8 +16,6 @@ package im.vector.riotx.features.settings.devices -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -41,9 +39,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.rx.rx -import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.features.crypto.verification.supportedVerificationMethods data class DevicesViewState( @@ -76,15 +72,6 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic private var _currentDeviceId: String? = null private var _currentSession: String? = null - private val _requestPasswordLiveData = MutableLiveData>() - val requestPasswordLiveData: LiveData> - get() = _requestPasswordLiveData - - // Used to communicate back from model to fragment - private val _requestLiveData = MutableLiveData>>() - val fragmentActionLiveData: LiveData>> - get() = _requestLiveData - init { refreshDevicesList() session.getVerificationService().addListener(this) @@ -187,25 +174,21 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic private fun handleVerify(action: DevicesAction.VerifyMyDevice) { val txID = session.getVerificationService().requestKeyVerification(supportedVerificationMethods, session.myUserId, listOf(action.deviceId)) - _requestLiveData.postValue(LiveEvent(Success( - action.copy( - userId = session.myUserId, - transactionId = txID.transactionId - ) - ))) + _viewEvents.post(DevicesViewEvents.ShowVerifyDevice( + session.myUserId, + txID.transactionId + )) } private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state -> val info = state.devices.invoke()?.firstOrNull { it.deviceId == action.deviceId } - if (info == null) { - _requestLiveData.postValue(LiveEvent(Uninitialized)) - } else { - _requestLiveData.postValue(LiveEvent(Success(action.copy(deviceInfo = info)))) + if (info != null) { + _viewEvents.post(DevicesViewEvents.PromptRenameDevice(info)) } } private fun handleRename(action: DevicesAction.Rename) { - session.setDeviceName(action.deviceInfo.deviceId!!, action.newName, object : MatrixCallback { + session.setDeviceName(action.deviceId, action.newName, object : MatrixCallback { override fun onSuccess(data: Unit) { setState { copy( @@ -261,7 +244,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic ) } - _requestPasswordLiveData.postLiveEvent(Unit) + _viewEvents.post(DevicesViewEvents.RequestPassword) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index a1cc93d0b0..603b0b6b78 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -23,7 +23,6 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo @@ -32,7 +31,6 @@ import im.vector.riotx.core.dialogs.PromptPasswordDialog import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.exhaustive -import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.crypto.verification.VerificationBottomSheet @@ -64,35 +62,18 @@ class VectorSettingsDevicesFragment @Inject constructor( recyclerView.configureWith(devicesController, showDivider = true) viewModel.observeViewEvents { when (it) { - is DevicesViewEvents.Loading -> showLoading(it.message) - is DevicesViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive - } - viewModel.requestPasswordLiveData.observeEvent(this) { - maybeShowDeleteDeviceWithPasswordDialog() - } - - viewModel.fragmentActionLiveData.observeEvent(this) { async -> - when (async) { - is Success -> { - when (val action = async.invoke()) { - is DevicesAction.PromptRename -> { - action.deviceInfo?.let { deviceInfo -> - displayDeviceRenameDialog(deviceInfo) - } - } - is DevicesAction.VerifyMyDevice -> { - if (context is VectorBaseActivity) { - VerificationBottomSheet.withArgs( - roomId = null, - otherUserId = action.userId!!, - transactionId = action.transactionId!! - ).show(childFragmentManager, "REQPOP") - } - } - } + is DevicesViewEvents.Loading -> showLoading(it.message) + is DevicesViewEvents.Failure -> showFailure(it.throwable) + is DevicesViewEvents.RequestPassword -> maybeShowDeleteDeviceWithPasswordDialog() + is DevicesViewEvents.PromptRenameDevice -> displayDeviceRenameDialog(it.deviceInfo) + is DevicesViewEvents.ShowVerifyDevice -> { + VerificationBottomSheet.withArgs( + roomId = null, + otherUserId = it.userId, + transactionId = it.transactionId + ).show(childFragmentManager, "REQPOP") } - } + }.exhaustive } } @@ -152,7 +133,7 @@ class VectorSettingsDevicesFragment @Inject constructor( .setPositiveButton(R.string.ok) { _, _ -> val newName = input.text.toString() - viewModel.handle(DevicesAction.Rename(deviceInfo, newName)) + viewModel.handle(DevicesAction.Rename(deviceInfo.deviceId!!, newName)) } .setNegativeButton(R.string.cancel, null) .show()