mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-31 03:17:13 +01:00
Merge pull request #976 from vector-im/feature/viewEvents
Use View events
This commit is contained in:
commit
6750237764
@ -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 <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
@ -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<CreateDirectRoomViewState, CreateDirectRoomAction, EmptyViewEvents>(initialState) {
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -62,10 +51,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||
|
||||
private val _selectUserEvent = MutableLiveData<LiveEvent<SelectUserAction>>()
|
||||
val selectUserEvent: LiveData<LiveEvent<SelectUserAction>>
|
||||
get() = _selectUserEvent
|
||||
|
||||
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||
|
||||
@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() {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
@ -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<VerificationBottomSheetViewState, VerificationAction, EmptyViewEvents>(initialState),
|
||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
|
||||
VerificationService.VerificationListener {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<VerificationAction>>>
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
@ -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<GroupListViewState, GroupListAction, EmptyViewEvents>(initialState) {
|
||||
) : VectorViewModel<GroupListViewState, GroupListAction, GroupListViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -62,9 +57,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||
}
|
||||
}
|
||||
|
||||
private val _openGroupLiveData = MutableLiveData<LiveEvent<GroupSummary>>()
|
||||
val openGroupLiveData: LiveData<LiveEvent<GroupSummary>>
|
||||
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)
|
||||
|
@ -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<RoomDetailAction>) {
|
||||
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
|
||||
|
@ -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<String>) : 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()
|
||||
}
|
||||
|
@ -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<LiveEvent<Async<RoomDetailAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>>
|
||||
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<LiveEvent<Pair<Int, List<Any>>>>()
|
||||
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
|
||||
get() = _nonBlockingPopAlert
|
||||
|
||||
private val _sendMessageResultLiveData = MutableLiveData<LiveEvent<SendMessageResult>>()
|
||||
val sendMessageResultLiveData: LiveData<LiveEvent<SendMessageResult>>
|
||||
get() = _sendMessageResultLiveData
|
||||
|
||||
private val _navigateToEvent = MutableLiveData<LiveEvent<String>>()
|
||||
val navigateToEvent: LiveData<LiveEvent<String>>
|
||||
get() = _navigateToEvent
|
||||
|
||||
private val _fileTooBigEvent = MutableLiveData<LiveEvent<FileTooBigError>>()
|
||||
val fileTooBigEvent: LiveData<LiveEvent<FileTooBigError>>
|
||||
get() = _fileTooBigEvent
|
||||
|
||||
private val _downloadedFileEvent = MutableLiveData<LiveEvent<DownloadFileState>>()
|
||||
val downloadedFileEvent: LiveData<LiveEvent<DownloadFileState>>
|
||||
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})",
|
||||
"<span data-mx-spoiler>${slashCommandResult.message}</span>"
|
||||
)
|
||||
_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<Unit> {
|
||||
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<Unit> {
|
||||
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<File> {
|
||||
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<Unit> {
|
||||
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<Unit> {
|
||||
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
|
||||
))))
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
@ -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<PublicRoomsViewState, RoomDirectoryAction, EmptyViewEvents>(initialState) {
|
||||
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -58,10 +59,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
}
|
||||
}
|
||||
|
||||
private val _joinRoomErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
|
||||
val joinRoomErrorLiveData: LiveData<LiveEvent<Throwable>>
|
||||
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(
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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<LiveEvent<Async<RoomMemberProfileAction>>>()
|
||||
val actionResultLiveData: LiveData<LiveEvent<Async<RoomMemberProfileAction>>>
|
||||
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()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
@ -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<DeviceListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<VerificationAction>>>
|
||||
get() = _requestLiveData
|
||||
: VectorViewModel<DeviceListViewState, DeviceListAction, DeviceListBottomSheetViewEvents>(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<DeviceListBottomSheetViewModel, DeviceListViewState> {
|
||||
@JvmStatic
|
||||
|
@ -62,6 +62,6 @@ class DeviceListFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onDeviceSelected(device: CryptoDeviceInfo) {
|
||||
viewModel.selectDevice(device)
|
||||
viewModel.handle(DeviceListAction.SelectDevice(device))
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,6 @@ class DeviceTrustInfoActionFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onVerifyManually(device: CryptoDeviceInfo) {
|
||||
viewModel.manuallyVerify(device)
|
||||
viewModel.handle(DeviceListAction.ManuallyVerify(device.deviceId))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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<CrossSigningSettingsViewState, CrossSigningAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<CrossSigningAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<CrossSigningAction>>>
|
||||
get() = _requestLiveData
|
||||
: VectorViewModel<CrossSigningSettingsViewState, CrossSigningAction, CrossSigningSettingsViewEvents>(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<Unit> {
|
||||
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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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<LiveEvent<Unit>>()
|
||||
val requestPasswordLiveData: LiveData<LiveEvent<Unit>>
|
||||
get() = _requestPasswordLiveData
|
||||
|
||||
// Used to communicate back from model to fragment
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<DevicesAction>>>()
|
||||
val fragmentActionLiveData: LiveData<LiveEvent<Async<DevicesAction>>>
|
||||
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<Unit> {
|
||||
session.setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user