Compare commits
3 Commits
develop
...
feature/fg
Author | SHA1 | Date |
---|---|---|
ganfra | 12b681209f | |
ganfra | 2acfce2d20 | |
ganfra | ebd491c6f0 |
|
@ -65,6 +65,11 @@ interface Room {
|
||||||
*/
|
*/
|
||||||
fun roomSummary(): RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspending version of [roomSummary] method
|
||||||
|
*/
|
||||||
|
suspend fun awaitRoomSummary(): RoomSummary?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this room as a Space, if the type is correct.
|
* Use this room as a Space, if the type is correct.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -92,6 +92,13 @@ interface RoomService {
|
||||||
*/
|
*/
|
||||||
fun getRoom(roomId: String): Room?
|
fun getRoom(roomId: String): Room?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspending version of [getRoom] method.
|
||||||
|
* @param roomId the roomId to look for.
|
||||||
|
* @return a room with roomId or null
|
||||||
|
*/
|
||||||
|
suspend fun awaitRoom(roomId: String): Room?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a roomSummary from a roomId or a room alias.
|
* Get a roomSummary from a roomId or a room alias.
|
||||||
* @param roomIdOrAlias the roomId or the alias of a room to look for.
|
* @param roomIdOrAlias the roomId or the alias of a room to look for.
|
||||||
|
|
|
@ -82,6 +82,10 @@ internal class DefaultRoom(
|
||||||
return roomSummaryDataSource.getRoomSummary(roomId)
|
return roomSummaryDataSource.getRoomSummary(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun awaitRoomSummary(): RoomSummary? {
|
||||||
|
return roomSummaryDataSource.awaitRoomSummary(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun asSpace(): Space? {
|
override fun asSpace(): Space? {
|
||||||
if (roomSummary()?.roomType != RoomType.SPACE) return null
|
if (roomSummary()?.roomType != RoomType.SPACE) return null
|
||||||
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
|
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
|
||||||
|
|
|
@ -82,6 +82,10 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomGetter.getRoom(roomId)
|
return roomGetter.getRoom(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun awaitRoom(roomId: String): Room? {
|
||||||
|
return roomGetter.awaitRoom(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getExistingDirectRoomWithUser(otherUserId: String): String? {
|
override fun getExistingDirectRoomWithUser(otherUserId: String): String? {
|
||||||
return roomGetter.getDirectRoomWith(otherUserId)
|
return roomGetter.getDirectRoomWith(otherUserId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.matrix.android.sdk.internal.session.room
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
|
@ -30,13 +32,16 @@ import javax.inject.Inject
|
||||||
internal interface RoomGetter {
|
internal interface RoomGetter {
|
||||||
fun getRoom(roomId: String): Room?
|
fun getRoom(roomId: String): Room?
|
||||||
|
|
||||||
|
suspend fun awaitRoom(roomId: String): Room?
|
||||||
|
|
||||||
fun getDirectRoomWith(otherUserId: String): String?
|
fun getDirectRoomWith(otherUserId: String): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultRoomGetter @Inject constructor(
|
internal class DefaultRoomGetter @Inject constructor(
|
||||||
private val realmSessionProvider: RealmSessionProvider,
|
private val realmSessionProvider: RealmSessionProvider,
|
||||||
private val roomFactory: RoomFactory
|
private val roomFactory: RoomFactory,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
) : RoomGetter {
|
) : RoomGetter {
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
|
@ -45,6 +50,10 @@ internal class DefaultRoomGetter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun awaitRoom(roomId: String) = withContext(coroutineDispatchers.io) {
|
||||||
|
getRoom(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getDirectRoomWith(otherUserId: String): String? {
|
override fun getDirectRoomWith(otherUserId: String): String? {
|
||||||
return realmSessionProvider.withRealm { realm ->
|
return realmSessionProvider.withRealm { realm ->
|
||||||
RoomSummaryEntity.where(realm)
|
RoomSummaryEntity.where(realm)
|
||||||
|
|
|
@ -51,7 +51,7 @@ internal class DefaultLeaveRoomTask @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun leaveRoom(roomId: String, reason: String?) {
|
private suspend fun leaveRoom(roomId: String, reason: String?) {
|
||||||
val roomSummary = roomSummaryDataSource.getRoomSummary(roomId)
|
val roomSummary = roomSummaryDataSource.awaitRoomSummary(roomId)
|
||||||
if (roomSummary?.membership?.isActive() == false) {
|
if (roomSummary?.membership?.isActive() == false) {
|
||||||
Timber.v("Room $roomId is not joined so can't be left")
|
Timber.v("Room $roomId is not joined so can't be left")
|
||||||
return
|
return
|
||||||
|
|
|
@ -26,6 +26,8 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||||
|
@ -58,6 +60,7 @@ internal class RoomSummaryDataSource @Inject constructor(
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val roomSummaryMapper: RoomSummaryMapper,
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val queryStringValueProcessor: QueryStringValueProcessor,
|
private val queryStringValueProcessor: QueryStringValueProcessor,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
|
@ -75,6 +78,10 @@ internal class RoomSummaryDataSource @Inject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun awaitRoomSummary(roomIdOrAlias: String) = withContext(coroutineDispatchers.io) {
|
||||||
|
getRoomSummary(roomIdOrAlias)
|
||||||
|
}
|
||||||
|
|
||||||
fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>> {
|
fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
||||||
|
|
|
@ -53,7 +53,7 @@ internal class DefaultSpace(
|
||||||
) {
|
) {
|
||||||
// Find best via
|
// Find best via
|
||||||
val bestVia = viaServers
|
val bestVia = viaServers
|
||||||
?: (spaceSummaryDataSource.getRoomSummary(roomId)
|
?: (spaceSummaryDataSource.awaitRoomSummary(roomId)
|
||||||
?.takeIf { it.joinRules == RoomJoinRules.RESTRICTED }
|
?.takeIf { it.joinRules == RoomJoinRules.RESTRICTED }
|
||||||
?.let {
|
?.let {
|
||||||
// for restricted room, best to take via from users that can invite in the
|
// for restricted room, best to take via from users that can invite in the
|
||||||
|
|
|
@ -246,7 +246,7 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
// and if client want to bypass, it could use sendStateEvent directly?
|
// and if client want to bypass, it could use sendStateEvent directly?
|
||||||
if (canonical) {
|
if (canonical) {
|
||||||
// check that we can send m.child in the parent room
|
// check that we can send m.child in the parent room
|
||||||
if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) {
|
if (roomSummaryDataSource.awaitRoomSummary(parentSpaceId)?.membership != Membership.JOIN) {
|
||||||
throw UnsupportedOperationException("Cannot add canonical child if not member of parent")
|
throw UnsupportedOperationException("Cannot add canonical child if not member of parent")
|
||||||
}
|
}
|
||||||
val powerLevelsEvent = stateEventDataSource.getStateEvent(
|
val powerLevelsEvent = stateEventDataSource.getStateEvent(
|
||||||
|
|
|
@ -58,7 +58,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
|
||||||
protocolsChecker.awaitCheckProtocols()
|
protocolsChecker.awaitCheckProtocols()
|
||||||
if (!protocolsChecker.supportVirtualRooms) return
|
if (!protocolsChecker.supportVirtualRooms) return
|
||||||
val invitedRoom = session.getRoom(invitedRoomId) ?: return
|
val invitedRoom = session.getRoom(invitedRoomId) ?: return
|
||||||
val inviterId = invitedRoom.roomSummary()?.inviterId ?: return
|
val inviterId = invitedRoom.awaitRoomSummary()?.inviterId ?: return
|
||||||
val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return
|
val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return
|
||||||
if (nativeLookup.fields.containsKey("is_virtual")) {
|
if (nativeLookup.fields.containsKey("is_virtual")) {
|
||||||
val nativeUser = nativeLookup.userId
|
val nativeUser = nativeLookup.userId
|
||||||
|
|
|
@ -213,6 +213,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.billcarsonfr.jsonviewer.JSonViewerDialog
|
import org.billcarsonfr.jsonviewer.JSonViewerDialog
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
|
@ -381,6 +382,7 @@ class TimelineFragment @Inject constructor(
|
||||||
)
|
)
|
||||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||||
lazyLoadedViews.bind(views)
|
lazyLoadedViews.bind(views)
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
setupToolbar(views.roomToolbar)
|
setupToolbar(views.roomToolbar)
|
||||||
.allowBack()
|
.allowBack()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
|
@ -393,6 +395,7 @@ class TimelineFragment @Inject constructor(
|
||||||
setupRemoveJitsiWidgetView()
|
setupRemoveJitsiWidgetView()
|
||||||
setupVoiceMessageView()
|
setupVoiceMessageView()
|
||||||
setupLiveLocationIndicator()
|
setupLiveLocationIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
|
views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
|
||||||
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
|
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
|
||||||
|
@ -979,16 +982,17 @@ class TimelineFragment @Inject constructor(
|
||||||
private fun setupJumpToBottomView() {
|
private fun setupJumpToBottomView() {
|
||||||
views.jumpToBottomView.visibility = View.INVISIBLE
|
views.jumpToBottomView.visibility = View.INVISIBLE
|
||||||
views.jumpToBottomView.debouncedClicks {
|
views.jumpToBottomView.debouncedClicks {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
timelineViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
timelineViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
||||||
views.jumpToBottomView.visibility = View.INVISIBLE
|
views.jumpToBottomView.visibility = View.INVISIBLE
|
||||||
if (!timelineViewModel.timeline.isLive) {
|
if (!timelineViewModel.timeline.await().isLive) {
|
||||||
scrollOnNewMessageCallback.forceScrollOnNextUpdate()
|
scrollOnNewMessageCallback.forceScrollOnNextUpdate()
|
||||||
timelineViewModel.timeline.restartWithEventId(null)
|
timelineViewModel.timeline.await().restartWithEventId(null)
|
||||||
} else {
|
} else {
|
||||||
layoutManager.scrollToPosition(0)
|
layoutManager.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager(
|
jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager(
|
||||||
views.jumpToBottomView,
|
views.jumpToBottomView,
|
||||||
debouncer,
|
debouncer,
|
||||||
|
@ -1216,6 +1220,7 @@ class TimelineFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSearchAction() {
|
private fun handleSearchAction() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
navigator.openSearch(
|
navigator.openSearch(
|
||||||
context = requireContext(),
|
context = requireContext(),
|
||||||
roomId = timelineArgs.roomId,
|
roomId = timelineArgs.roomId,
|
||||||
|
@ -1223,6 +1228,7 @@ class TimelineFragment @Inject constructor(
|
||||||
roomAvatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
|
roomAvatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun displayDisabledIntegrationDialog() {
|
private fun displayDisabledIntegrationDialog() {
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
@ -1416,9 +1422,9 @@ class TimelineFragment @Inject constructor(
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private suspend fun setupRecyclerView() {
|
||||||
timelineEventController.callback = this
|
timelineEventController.callback = this
|
||||||
timelineEventController.timeline = timelineViewModel.timeline
|
timelineEventController.timeline = timelineViewModel.timeline.await()
|
||||||
|
|
||||||
views.timelineRecyclerView.trackItemsVisibilityChange()
|
views.timelineRecyclerView.trackItemsVisibilityChange()
|
||||||
layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
|
layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
|
||||||
|
@ -2421,7 +2427,9 @@ class TimelineFragment @Inject constructor(
|
||||||
views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
|
views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
|
||||||
views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1)
|
views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1)
|
||||||
} else {
|
} else {
|
||||||
val roomMember = timelineViewModel.getMember(userId)
|
val roomMember = runBlocking {
|
||||||
|
timelineViewModel.getMember(userId)
|
||||||
|
}
|
||||||
// TODO move logic outside of fragment
|
// TODO move logic outside of fragment
|
||||||
(roomMember?.displayName ?: userId)
|
(roomMember?.displayName ?: userId)
|
||||||
.let { sanitizeDisplayName(it) }
|
.let { sanitizeDisplayName(it) }
|
||||||
|
@ -2491,20 +2499,23 @@ class TimelineFragment @Inject constructor(
|
||||||
* using the ThreadsActivity.
|
* using the ThreadsActivity.
|
||||||
*/
|
*/
|
||||||
private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false, showKeyboard: Boolean = false) {
|
private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false, showKeyboard: Boolean = false) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction())
|
analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction())
|
||||||
context?.let {
|
context?.let {
|
||||||
|
val roomSummary = timelineViewModel.awaitState().asyncRoomSummary()
|
||||||
val roomThreadDetailArgs = ThreadTimelineArgs(
|
val roomThreadDetailArgs = ThreadTimelineArgs(
|
||||||
startsThread = startsThread,
|
startsThread = startsThread,
|
||||||
roomId = timelineArgs.roomId,
|
roomId = timelineArgs.roomId,
|
||||||
displayName = timelineViewModel.getRoomSummary()?.displayName,
|
displayName = roomSummary?.displayName,
|
||||||
avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl,
|
avatarUrl = roomSummary?.avatarUrl,
|
||||||
roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
|
roomEncryptionTrustLevel = roomSummary?.roomEncryptionTrustLevel,
|
||||||
rootThreadEventId = rootThreadEventId,
|
rootThreadEventId = rootThreadEventId,
|
||||||
showKeyboard = showKeyboard
|
showKeyboard = showKeyboard
|
||||||
)
|
)
|
||||||
navigator.openThread(it, roomThreadDetailArgs)
|
navigator.openThread(it, roomThreadDetailArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun displayThreadsBetaOptInDialog() {
|
private fun displayThreadsBetaOptInDialog() {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
|
@ -2530,17 +2541,20 @@ class TimelineFragment @Inject constructor(
|
||||||
* using the ThreadsActivity.
|
* using the ThreadsActivity.
|
||||||
*/
|
*/
|
||||||
private fun navigateToThreadList() {
|
private fun navigateToThreadList() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction())
|
analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction())
|
||||||
context?.let {
|
context?.let {
|
||||||
|
val roomSummary = timelineViewModel.awaitState().asyncRoomSummary()
|
||||||
val roomThreadDetailArgs = ThreadTimelineArgs(
|
val roomThreadDetailArgs = ThreadTimelineArgs(
|
||||||
roomId = timelineArgs.roomId,
|
roomId = timelineArgs.roomId,
|
||||||
displayName = timelineViewModel.getRoomSummary()?.displayName,
|
displayName = roomSummary?.displayName,
|
||||||
roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
|
roomEncryptionTrustLevel = roomSummary?.roomEncryptionTrustLevel,
|
||||||
avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
|
avatarUrl = roomSummary?.avatarUrl
|
||||||
)
|
)
|
||||||
navigator.openThreadList(it, roomThreadDetailArgs)
|
navigator.openThreadList(it, roomThreadDetailArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VectorInviteView.Callback
|
// VectorInviteView.Callback
|
||||||
override fun onAcceptInvite() {
|
override fun onAcceptInvite() {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.AppStateHandler
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
import im.vector.app.core.mvrx.runCatchingToAsync
|
import im.vector.app.core.mvrx.runCatchingToAsync
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
@ -63,8 +64,10 @@ import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.space
|
import im.vector.app.space
|
||||||
import im.vector.lib.core.utils.flow.chunk
|
import im.vector.lib.core.utils.flow.chunk
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
@ -90,7 +93,6 @@ import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
|
||||||
import org.matrix.android.sdk.api.session.room.getStateEvent
|
import org.matrix.android.sdk.api.session.room.getStateEvent
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
|
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
|
||||||
|
@ -136,17 +138,22 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
||||||
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
timelineFactory: TimelineFactory,
|
timelineFactory: TimelineFactory,
|
||||||
appStateHandler: AppStateHandler,
|
appStateHandler: AppStateHandler,
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = viewModelScope.async(start = CoroutineStart.LAZY) {
|
||||||
|
session.roomService().awaitRoom(initialState.roomId)!!
|
||||||
|
}
|
||||||
|
val timeline = viewModelScope.async(start = CoroutineStart.LAZY) {
|
||||||
|
timelineFactory.createTimeline(viewModelScope, room, eventId, initialState.rootThreadEventId)
|
||||||
|
}
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>()
|
private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||||
private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>()
|
private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||||
private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0)
|
private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0)
|
||||||
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId, initialState.rootThreadEventId)
|
|
||||||
|
|
||||||
// Same lifecycle than the ViewModel (survive to screen rotation)
|
// Same lifecycle than the ViewModel (survive to screen rotation)
|
||||||
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
||||||
|
@ -175,8 +182,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
timeline.start(initialState.rootThreadEventId)
|
viewModelScope.launch {
|
||||||
timeline.addListener(this)
|
timeline.await().also {
|
||||||
|
it.start(initialState.rootThreadEventId)
|
||||||
|
it.addListener(this@TimelineViewModel)
|
||||||
|
}
|
||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
observeMembershipChanges()
|
observeMembershipChanges()
|
||||||
observeSummaryState()
|
observeSummaryState()
|
||||||
|
@ -189,20 +199,17 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
observeActiveRoomWidgets()
|
observeActiveRoomWidgets()
|
||||||
observePowerLevel()
|
observePowerLevel()
|
||||||
setupPreviewUrlObservers()
|
setupPreviewUrlObservers()
|
||||||
room.getRoomSummaryLive()
|
withContext(coroutineDispatchers.io) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
tryOrNull { room.await().readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
|
||||||
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
|
|
||||||
}
|
|
||||||
// Inform the SDK that the room is displayed
|
// Inform the SDK that the room is displayed
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
tryOrNull { session.roomService().onRoomDisplayed(initialState.roomId) }
|
tryOrNull { session.roomService().onRoomDisplayed(initialState.roomId) }
|
||||||
}
|
}
|
||||||
callManager.addProtocolsCheckerListener(this)
|
callManager.addProtocolsCheckerListener(this@TimelineViewModel)
|
||||||
callManager.checkForProtocolsSupportIfNeeded()
|
callManager.checkForProtocolsSupportIfNeeded()
|
||||||
chatEffectManager.delegate = this
|
chatEffectManager.delegate = this@TimelineViewModel
|
||||||
|
|
||||||
// Ensure to share the outbound session keys with all members
|
// Ensure to share the outbound session keys with all members
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.await().roomCryptoService().isEncrypted()) {
|
||||||
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
||||||
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
||||||
if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
|
if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
|
||||||
|
@ -220,7 +227,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
// We are coming from a notification, try to switch to the most relevant space
|
// We are coming from a notification, try to switch to the most relevant space
|
||||||
// so that when hitting back the room will appear in the list
|
// so that when hitting back the room will appear in the list
|
||||||
appStateHandler.getCurrentRoomGroupingMethod()?.space().let { currentSpace ->
|
appStateHandler.getCurrentRoomGroupingMethod()?.space().let { currentSpace ->
|
||||||
val currentRoomSummary = room.roomSummary() ?: return@let
|
val currentRoomSummary = room.await().awaitRoomSummary() ?: return@let
|
||||||
// nothing we are good
|
// nothing we are good
|
||||||
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) ||
|
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) ||
|
||||||
(currentSpace != null && !currentRoomSummary.flattenParentIds.contains(currentSpace.roomId))) {
|
(currentSpace != null && !currentRoomSummary.flattenParentIds.contains(currentSpace.roomId))) {
|
||||||
|
@ -240,13 +247,14 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
initThreads()
|
initThreads()
|
||||||
|
|
||||||
// Observe location service lifecycle to be able to warn the user
|
// Observe location service lifecycle to be able to warn the user
|
||||||
locationSharingServiceConnection.bind(this)
|
locationSharingServiceConnection.bind(this@TimelineViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Threads specific initialization.
|
* Threads specific initialization.
|
||||||
*/
|
*/
|
||||||
private fun initThreads() {
|
private suspend fun initThreads() {
|
||||||
markThreadTimelineAsReadLocal()
|
markThreadTimelineAsReadLocal()
|
||||||
observeLocalThreadNotifications()
|
observeLocalThreadNotifications()
|
||||||
}
|
}
|
||||||
|
@ -259,13 +267,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareForEncryption() {
|
private suspend fun prepareForEncryption() {
|
||||||
// check if there is not already a call made, or if there has been an error
|
// check if there is not already a call made, or if there has been an error
|
||||||
if (prepareToEncrypt.shouldLoad) {
|
if (prepareToEncrypt.shouldLoad) {
|
||||||
prepareToEncrypt = Loading()
|
prepareToEncrypt = Loading()
|
||||||
viewModelScope.launch {
|
|
||||||
runCatching {
|
runCatching {
|
||||||
room.roomCryptoService().prepareToEncrypt()
|
room.await().roomCryptoService().prepareToEncrypt()
|
||||||
}.fold({
|
}.fold({
|
||||||
prepareToEncrypt = Success(Unit)
|
prepareToEncrypt = Success(Unit)
|
||||||
}, {
|
}, {
|
||||||
|
@ -273,13 +280,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun observePowerLevel() {
|
private suspend fun observePowerLevel() {
|
||||||
PowerLevelsFlowFactory(room).createFlow()
|
PowerLevelsFlowFactory(room.await()).createFlow()
|
||||||
.onEach {
|
.onEach {
|
||||||
val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId)
|
val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId)
|
||||||
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(initialState.roomId)
|
||||||
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
||||||
val isAllowedToSetupEncryption = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
val isAllowedToSetupEncryption = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
||||||
setState {
|
setState {
|
||||||
|
@ -290,7 +296,9 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
isAllowedToSetupEncryption = isAllowedToSetupEncryption
|
isAllowedToSetupEncryption = isAllowedToSetupEncryption
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.launchIn(viewModelScope)
|
}
|
||||||
|
.flowOn(coroutineDispatchers.computation)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeActiveRoomWidgets() {
|
private fun observeActiveRoomWidgets() {
|
||||||
|
@ -302,6 +310,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
.map { widgets ->
|
.map { widgets ->
|
||||||
widgets.filter { it.isActive }
|
widgets.filter { it.isActive }
|
||||||
}
|
}
|
||||||
|
.flowOn(coroutineDispatchers.computation)
|
||||||
.execute { widgets ->
|
.execute { widgets ->
|
||||||
copy(activeRoomWidgets = widgets)
|
copy(activeRoomWidgets = widgets)
|
||||||
}
|
}
|
||||||
|
@ -323,11 +332,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeMyRoomMember() {
|
private suspend fun observeMyRoomMember() {
|
||||||
val queryParams = roomMemberQueryParams {
|
val queryParams = roomMemberQueryParams {
|
||||||
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
||||||
}
|
}
|
||||||
room.flow()
|
room.await().flow()
|
||||||
.liveRoomMembers(queryParams)
|
.liveRoomMembers(queryParams)
|
||||||
.map {
|
.map {
|
||||||
it.firstOrNull().toOptional()
|
it.firstOrNull().toOptional()
|
||||||
|
@ -338,13 +347,13 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPreviewUrlObservers() {
|
private suspend fun setupPreviewUrlObservers() {
|
||||||
if (!vectorPreferences.showUrlPreviews()) {
|
if (!vectorPreferences.showUrlPreviews()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
combine(
|
combine(
|
||||||
timelineEvents,
|
timelineEvents,
|
||||||
room.flow().liveRoomSummary()
|
room.await().flow().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map { it.isEncrypted }
|
.map { it.isEncrypted }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
@ -352,7 +361,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
if (isRoomEncrypted) {
|
if (isRoomEncrypted) {
|
||||||
return@combine
|
return@combine
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Default) {
|
withContext(coroutineDispatchers.computation) {
|
||||||
Timber.v("On new timeline events for urlpreview on ${Thread.currentThread()}")
|
Timber.v("On new timeline events for urlpreview on ${Thread.currentThread()}")
|
||||||
snapshot.forEach {
|
snapshot.forEach {
|
||||||
previewUrlRetriever.getPreviewUrl(it)
|
previewUrlRetriever.getPreviewUrl(it)
|
||||||
|
@ -369,7 +378,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
private fun markThreadTimelineAsReadLocal() {
|
private fun markThreadTimelineAsReadLocal() {
|
||||||
initialState.rootThreadEventId?.let {
|
initialState.rootThreadEventId?.let {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
room.threadsLocalService().markThreadAsRead(it)
|
room.await().threadsLocalService().markThreadAsRead(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,8 +386,8 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
/**
|
/**
|
||||||
* Observe local unread threads.
|
* Observe local unread threads.
|
||||||
*/
|
*/
|
||||||
private fun observeLocalThreadNotifications() {
|
private suspend fun observeLocalThreadNotifications() {
|
||||||
room.flow()
|
room.await().flow()
|
||||||
.liveLocalUnreadThreadList()
|
.liveLocalUnreadThreadList()
|
||||||
.execute {
|
.execute {
|
||||||
val threadList = it.invoke()
|
val threadList = it.invoke()
|
||||||
|
@ -395,11 +404,10 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOtherUserIds() = room.roomSummary()?.otherMemberIds
|
suspend fun getRoomSummary() = room.await().awaitRoomSummary()
|
||||||
|
|
||||||
fun getRoomSummary() = room.roomSummary()
|
|
||||||
|
|
||||||
override fun handle(action: RoomDetailAction) {
|
override fun handle(action: RoomDetailAction) {
|
||||||
|
viewModelScope.launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action)
|
is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action)
|
||||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||||
|
@ -467,6 +475,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
RoomDetailAction.StopLiveLocationSharing -> handleStopLiveLocationSharing()
|
RoomDetailAction.StopLiveLocationSharing -> handleStopLiveLocationSharing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
|
private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
|
||||||
if (state.jitsiState.confId == null) {
|
if (state.jitsiState.confId == null) {
|
||||||
|
@ -505,10 +514,10 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
previewUrlRetriever.doNotShowPreviewUrlFor(action.eventId, action.url)
|
previewUrlRetriever.doNotShowPreviewUrlFor(action.eventId, action.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
|
private suspend fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
room.stateService().updateAvatar(action.newAvatarUri, action.newAvatarFileName)
|
room.await().stateService().updateAvatar(action.newAvatarUri, action.newAvatarFileName)
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
||||||
|
@ -524,12 +533,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog)
|
_viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
|
private suspend fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
|
||||||
room.readService().getUserReadReceipt(action.userId)
|
room.await().readService().getUserReadReceipt(action.userId)
|
||||||
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
|
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
private suspend fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
||||||
val content = initialState.rootThreadEventId?.let {
|
val content = initialState.rootThreadEventId?.let {
|
||||||
action.stickerContent.copy(
|
action.stickerContent.copy(
|
||||||
relatesTo = RelationDefaultContent(
|
relatesTo = RelationDefaultContent(
|
||||||
|
@ -540,31 +549,27 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
} ?: action.stickerContent
|
} ?: action.stickerContent
|
||||||
|
|
||||||
room.sendService().sendEvent(EventType.STICKER, content.toContent())
|
room.await().sendService().sendEvent(EventType.STICKER, content.toContent())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartCall(action: RoomDetailAction.StartCall) {
|
private suspend fun handleStartCall(action: RoomDetailAction.StartCall) {
|
||||||
viewModelScope.launch {
|
val room = room.await()
|
||||||
room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
|
room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
|
||||||
callManager.startOutgoingCall(room.roomId, it, action.isVideo)
|
callManager.startOutgoingCall(room.roomId, it, action.isVideo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleEndCall() {
|
private fun handleEndCall() {
|
||||||
callManager.endCallForRoom(initialState.roomId)
|
callManager.endCallForRoom(initialState.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSelectStickerAttachment() {
|
private suspend fun handleSelectStickerAttachment() {
|
||||||
viewModelScope.launch {
|
|
||||||
val viewEvent = stickerPickerActionHandler.handle()
|
val viewEvent = stickerPickerActionHandler.handle()
|
||||||
_viewEvents.post(viewEvent)
|
_viewEvents.post(viewEvent)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleOpenIntegrationManager() {
|
private suspend fun handleOpenIntegrationManager() {
|
||||||
viewModelScope.launch {
|
val viewEvent = withContext(coroutineDispatchers.computation) {
|
||||||
val viewEvent = withContext(Dispatchers.Default) {
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
RoomDetailViewEvents.OpenIntegrationManager
|
RoomDetailViewEvents.OpenIntegrationManager
|
||||||
} else {
|
} else {
|
||||||
|
@ -573,9 +578,9 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
_viewEvents.post(viewEvent)
|
_viewEvents.post(viewEvent)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleManageIntegrations() = withState { state ->
|
private suspend fun handleManageIntegrations() {
|
||||||
|
val state = awaitState()
|
||||||
if (state.activeRoomWidgets().isNullOrEmpty()) {
|
if (state.activeRoomWidgets().isNullOrEmpty()) {
|
||||||
// Directly open integration manager screen
|
// Directly open integration manager screen
|
||||||
handleOpenIntegrationManager()
|
handleOpenIntegrationManager()
|
||||||
|
@ -585,11 +590,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
|
private suspend fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
val widget = jitsiService.createJitsiWidget(room.roomId, action.withVideo)
|
val widget = jitsiService.createJitsiWidget(initialState.roomId, action.withVideo)
|
||||||
_viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.withVideo))
|
_viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.withVideo))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget)))
|
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget)))
|
||||||
|
@ -599,16 +604,17 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeleteWidget(widgetId: String) = withState { state ->
|
private suspend fun handleDeleteWidget(widgetId: String) {
|
||||||
|
val state = awaitState()
|
||||||
val isJitsiWidget = state.jitsiState.widgetId == widgetId
|
val isJitsiWidget = state.jitsiState.widgetId == widgetId
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
if (isJitsiWidget) {
|
if (isJitsiWidget) {
|
||||||
setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) }
|
setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) }
|
||||||
} else {
|
} else {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
||||||
}
|
}
|
||||||
session.widgetService().destroyRoomWidget(room.roomId, widgetId)
|
session.widgetService().destroyRoomWidget(initialState.roomId, widgetId)
|
||||||
// local echo
|
// local echo
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -660,7 +666,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
if (trackUnreadMessages.getAndSet(false)) {
|
if (trackUnreadMessages.getAndSet(false)) {
|
||||||
mostRecentDisplayedEvent?.root?.eventId?.also {
|
mostRecentDisplayedEvent?.root?.eventId?.also {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
tryOrNull { room.readService().setReadMarker(it) }
|
tryOrNull { room.await().readService().setReadMarker(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mostRecentDisplayedEvent = null
|
mostRecentDisplayedEvent = null
|
||||||
|
@ -672,13 +678,13 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
invisibleEventsSource.post(action)
|
invisibleEventsSource.post(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMember(userId: String): RoomMemberSummary? {
|
suspend fun getMember(userId: String): RoomMemberSummary? {
|
||||||
return room.membershipService().getRoomMember(userId)
|
return room.await().membershipService().getRoomMember(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
|
private suspend fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
|
||||||
// Ensure outbound session keys
|
// Ensure outbound session keys
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.await().roomCryptoService().isEncrypted()) {
|
||||||
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
||||||
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
||||||
if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
|
if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
|
||||||
|
@ -689,11 +695,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleJoinAndOpenReplacementRoom() = withState { state ->
|
private suspend fun handleJoinAndOpenReplacementRoom() {
|
||||||
val tombstoneContent = state.tombstoneEvent?.getClearContent()?.toModel<RoomTombstoneContent>() ?: return@withState
|
val state = awaitState()
|
||||||
|
val tombstoneContent = state.tombstoneEvent?.getClearContent()?.toModel<RoomTombstoneContent>() ?: return
|
||||||
|
|
||||||
val roomId = tombstoneContent.replacementRoomId ?: ""
|
val roomId = tombstoneContent.replacementRoomId ?: ""
|
||||||
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
val isRoomJoined = session.roomService().awaitRoom(roomId)?.awaitRoomSummary()?.membership == Membership.JOIN
|
||||||
if (isRoomJoined) {
|
if (isRoomJoined) {
|
||||||
setState { copy(joinUpgradedRoomAsync = Success(roomId)) }
|
setState { copy(joinUpgradedRoomAsync = Success(roomId)) }
|
||||||
_viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
|
_viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
|
||||||
|
@ -706,7 +713,6 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(joinUpgradedRoomAsync = Loading())
|
copy(joinUpgradedRoomAsync = Loading())
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
|
||||||
val result = runCatchingToAsync {
|
val result = runCatchingToAsync {
|
||||||
session.roomService().joinRoom(roomId, viaServers = viaServers)
|
session.roomService().joinRoom(roomId, viaServers = viaServers)
|
||||||
roomId
|
roomId
|
||||||
|
@ -719,7 +725,6 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleClickMisconfiguredE2E() = withState { state ->
|
private fun handleClickMisconfiguredE2E() = withState { state ->
|
||||||
if (state.isAllowedToSetupEncryption) {
|
if (state.isAllowedToSetupEncryption) {
|
||||||
|
@ -761,37 +766,33 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun handleSendReaction(action: RoomDetailAction.SendReaction) {
|
private suspend fun handleSendReaction(action: RoomDetailAction.SendReaction) {
|
||||||
room.relationService().sendReaction(action.targetEventId, action.reaction)
|
room.await().relationService().sendReaction(action.targetEventId, action.reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
|
private suspend fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
|
||||||
val event = room.getTimelineEvent(action.targetEventId) ?: return
|
val event = room.await().getTimelineEvent(action.targetEventId) ?: return
|
||||||
room.sendService().redactEvent(event.root, action.reason)
|
room.await().sendService().redactEvent(event.root, action.reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
|
private suspend fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
|
||||||
viewModelScope.launch {
|
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
room.relationService().undoReaction(action.targetEventId, action.reaction)
|
room.await().relationService().undoReaction(action.targetEventId, action.reaction)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) {
|
private suspend fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) {
|
||||||
if (action.add) {
|
if (action.add) {
|
||||||
room.relationService().sendReaction(action.targetEventId, action.selectedReaction)
|
room.await().relationService().sendReaction(action.targetEventId, action.selectedReaction)
|
||||||
} else {
|
} else {
|
||||||
viewModelScope.launch {
|
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
room.relationService().undoReaction(action.targetEventId, action.selectedReaction)
|
room.await().relationService().undoReaction(action.targetEventId, action.selectedReaction)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendMedia(action: RoomDetailAction.SendMedia) {
|
private suspend fun handleSendMedia(action: RoomDetailAction.SendMedia) {
|
||||||
room.sendService().sendMedias(
|
room.await().sendService().sendMedias(
|
||||||
action.attachments,
|
action.attachments,
|
||||||
action.compressBeforeSending,
|
action.compressBeforeSending,
|
||||||
emptySet(),
|
emptySet(),
|
||||||
|
@ -799,14 +800,14 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
|
private suspend fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
withContext(coroutineDispatchers.computation) {
|
||||||
if (action.event.root.sendState.isSent()) { // ignore pending/local events
|
if (action.event.root.sendState.isSent()) { // ignore pending/local events
|
||||||
visibleEventsSource.post(action)
|
visibleEventsSource.post(action)
|
||||||
}
|
}
|
||||||
// We need to update this with the related m.replace also (to move read receipt)
|
// We need to update this with the related m.replace also (to move read receipt)
|
||||||
action.event.annotations?.editSummary?.sourceEvents?.forEach {
|
action.event.annotations?.editSummary?.sourceEvents?.forEach {
|
||||||
room.getTimelineEvent(it)?.let { event ->
|
room.await().getTimelineEvent(it)?.let { event ->
|
||||||
visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event))
|
visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -826,43 +827,40 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.StopChatEffects)
|
_viewEvents.post(RoomDetailViewEvents.StopChatEffects)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoadMore(action: RoomDetailAction.LoadMoreTimelineEvents) {
|
private suspend fun handleLoadMore(action: RoomDetailAction.LoadMoreTimelineEvents) {
|
||||||
timeline.paginate(action.direction, PAGINATION_COUNT)
|
timeline.await().paginate(action.direction, PAGINATION_COUNT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRejectInvite() {
|
private suspend fun handleRejectInvite() {
|
||||||
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
|
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
try {
|
||||||
session.roomService().leaveRoom(room.roomId)
|
session.roomService().leaveRoom(initialState.roomId)
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
|
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAcceptInvite() {
|
private suspend fun handleAcceptInvite() {
|
||||||
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
|
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
try {
|
||||||
session.roomService().joinRoom(room.roomId)
|
session.roomService().joinRoom(initialState.roomId)
|
||||||
trackRoomJoined()
|
trackRoomJoined()
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
|
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun trackRoomJoined() {
|
private suspend fun trackRoomJoined() {
|
||||||
val trigger = if (initialState.isInviteAlreadyAccepted) {
|
val trigger = if (initialState.isInviteAlreadyAccepted) {
|
||||||
JoinedRoom.Trigger.Invite
|
JoinedRoom.Trigger.Invite
|
||||||
} else {
|
} else {
|
||||||
JoinedRoom.Trigger.Timeline
|
JoinedRoom.Trigger.Timeline
|
||||||
}
|
}
|
||||||
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom(trigger))
|
val roomSummary = room.await().awaitRoomSummary()
|
||||||
|
analyticsTracker.capture(roomSummary.toAnalyticsJoinedRoom(trigger))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
|
private suspend fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
|
||||||
val mxcUrl = action.messageFileContent.getFileUrl() ?: return
|
val mxcUrl = action.messageFileContent.getFileUrl() ?: return
|
||||||
val isLocalSendingFile = action.senderId == session.myUserId &&
|
val isLocalSendingFile = action.senderId == session.myUserId &&
|
||||||
mxcUrl.startsWith("content://")
|
mxcUrl.startsWith("content://")
|
||||||
|
@ -876,7 +874,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModelScope.launch {
|
withContext(coroutineDispatchers.io) {
|
||||||
val fileState = session.fileService().fileState(action.messageFileContent)
|
val fileState = session.fileService().fileState(action.messageFileContent)
|
||||||
var canOpen = fileState is FileService.FileState.InCache && fileState.decryptedFileInCache
|
var canOpen = fileState is FileService.FileState.InCache && fileState.decryptedFileInCache
|
||||||
if (!canOpen) {
|
if (!canOpen) {
|
||||||
|
@ -910,12 +908,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) {
|
private suspend fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) {
|
||||||
val targetEventId: String = action.eventId
|
val targetEventId: String = action.eventId
|
||||||
val indexOfEvent = timeline.getIndexOfEvent(targetEventId)
|
val indexOfEvent = timeline.await().getIndexOfEvent(targetEventId)
|
||||||
if (indexOfEvent == null) {
|
if (indexOfEvent == null) {
|
||||||
// Event is not already in RAM
|
// Event is not already in RAM
|
||||||
timeline.restartWithEventId(targetEventId)
|
timeline.await().restartWithEventId(targetEventId)
|
||||||
}
|
}
|
||||||
if (action.highlight) {
|
if (action.highlight) {
|
||||||
setState { copy(highlightedEventId = targetEventId) }
|
setState { copy(highlightedEventId = targetEventId) }
|
||||||
|
@ -923,8 +921,9 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId))
|
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
|
private suspend fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
|
||||||
val targetEventId = action.eventId
|
val targetEventId = action.eventId
|
||||||
|
val room = room.await()
|
||||||
room.getTimelineEvent(targetEventId)?.let {
|
room.getTimelineEvent(targetEventId)?.let {
|
||||||
// State must be UNDELIVERED or Failed
|
// State must be UNDELIVERED or Failed
|
||||||
if (!it.root.sendState.hasFailed()) {
|
if (!it.root.sendState.hasFailed()) {
|
||||||
|
@ -941,8 +940,9 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemove(action: RoomDetailAction.RemoveFailedEcho) {
|
private suspend fun handleRemove(action: RoomDetailAction.RemoveFailedEcho) {
|
||||||
val targetEventId = action.eventId
|
val targetEventId = action.eventId
|
||||||
|
val room = room.await()
|
||||||
room.getTimelineEvent(targetEventId)?.let {
|
room.getTimelineEvent(targetEventId)?.let {
|
||||||
// State must be UNDELIVERED or Failed
|
// State must be UNDELIVERED or Failed
|
||||||
if (!it.root.sendState.hasFailed()) {
|
if (!it.root.sendState.hasFailed()) {
|
||||||
|
@ -953,7 +953,8 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCancel(action: RoomDetailAction.CancelSend) {
|
private suspend fun handleCancel(action: RoomDetailAction.CancelSend) {
|
||||||
|
val room = room.await()
|
||||||
if (action.force) {
|
if (action.force) {
|
||||||
room.sendService().cancelSend(action.eventId)
|
room.sendService().cancelSend(action.eventId)
|
||||||
return
|
return
|
||||||
|
@ -969,12 +970,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResendAll() {
|
private suspend fun handleResendAll() {
|
||||||
room.sendService().resendAllFailedMessages()
|
room.await().sendService().resendAllFailedMessages()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveAllFailedMessages() {
|
private suspend fun handleRemoveAllFailedMessages() {
|
||||||
room.sendService().cancelAllFailedMessages()
|
room.await().sendService().cancelAllFailedMessages()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeEventDisplayedActions() {
|
private fun observeEventDisplayedActions() {
|
||||||
|
@ -997,11 +998,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
|
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
tryOrNull { room.readService().setReadReceipt(eventId) }
|
tryOrNull { room.await().readService().setReadReceipt(eventId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.Default)
|
.flowOn(coroutineDispatchers.computation)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1009,33 +1010,27 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
* Returns the index of event in the timeline.
|
* Returns the index of event in the timeline.
|
||||||
* Returns Int.MAX_VALUE if not found
|
* Returns Int.MAX_VALUE if not found
|
||||||
*/
|
*/
|
||||||
private fun TimelineEvent.indexOfEvent(): Int = timeline.getIndexOfEvent(eventId) ?: Int.MAX_VALUE
|
private suspend fun TimelineEvent.indexOfEvent(): Int = timeline.await().getIndexOfEvent(eventId) ?: Int.MAX_VALUE
|
||||||
|
|
||||||
private fun handleMarkAllAsRead() {
|
private suspend fun handleMarkAllAsRead() {
|
||||||
setState { copy(unreadState = UnreadState.HasNoUnread) }
|
setState { copy(unreadState = UnreadState.HasNoUnread) }
|
||||||
viewModelScope.launch {
|
tryOrNull { room.await().readService().markAsRead(ReadService.MarkAsReadParams.BOTH) }
|
||||||
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReportContent(action: RoomDetailAction.ReportContent) {
|
private suspend fun handleReportContent(action: RoomDetailAction.ReportContent) {
|
||||||
viewModelScope.launch {
|
|
||||||
val event = try {
|
val event = try {
|
||||||
room.reportingService().reportContent(action.eventId, -100, action.reason)
|
room.await().reportingService().reportContent(action.eventId, -100, action.reason)
|
||||||
RoomDetailViewEvents.ActionSuccess(action)
|
RoomDetailViewEvents.ActionSuccess(action)
|
||||||
} catch (failure: Exception) {
|
} catch (failure: Exception) {
|
||||||
RoomDetailViewEvents.ActionFailure(action, failure)
|
RoomDetailViewEvents.ActionFailure(action, failure)
|
||||||
}
|
}
|
||||||
_viewEvents.post(event)
|
_viewEvents.post(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIgnoreUser(action: RoomDetailAction.IgnoreUser) {
|
private suspend fun handleIgnoreUser(action: RoomDetailAction.IgnoreUser) {
|
||||||
if (action.userId.isNullOrEmpty()) {
|
if (action.userId.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
val event = try {
|
val event = try {
|
||||||
session.userService().ignoreUserIds(listOf(action.userId))
|
session.userService().ignoreUserIds(listOf(action.userId))
|
||||||
RoomDetailViewEvents.ActionSuccess(action)
|
RoomDetailViewEvents.ActionSuccess(action)
|
||||||
|
@ -1044,14 +1039,13 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
_viewEvents.post(event)
|
_viewEvents.post(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||||
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${initialState.roomId}, txId:${action.transactionId}")
|
||||||
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
|
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
|
||||||
supportedVerificationMethodsProvider.provide(),
|
supportedVerificationMethodsProvider.provide(),
|
||||||
action.otherUserId,
|
action.otherUserId,
|
||||||
room.roomId,
|
initialState.roomId,
|
||||||
action.transactionId
|
action.transactionId
|
||||||
)) {
|
)) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||||
|
@ -1064,7 +1058,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
session.cryptoService().verificationService().declineVerificationRequestInDMs(
|
session.cryptoService().verificationService().declineVerificationRequestInDMs(
|
||||||
action.otherUserId,
|
action.otherUserId,
|
||||||
action.transactionId,
|
action.transactionId,
|
||||||
room.roomId
|
initialState.roomId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1075,7 +1069,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
|
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
|
||||||
// Check if this request is still active and handled by me
|
// Check if this request is still active and handled by me
|
||||||
session.cryptoService().verificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
|
session.cryptoService().verificationService().getExistingVerificationRequestInRoom(initialState.roomId, action.transactionId)?.let {
|
||||||
if (it.handledByOtherSession) return
|
if (it.handledByOtherSession) return
|
||||||
if (!it.isFinished) {
|
if (!it.isFinished) {
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
|
@ -1089,16 +1083,16 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReRequestKeys(action: RoomDetailAction.ReRequestKeys) {
|
private suspend fun handleReRequestKeys(action: RoomDetailAction.ReRequestKeys) {
|
||||||
// Check if this request is still active and handled by me
|
// Check if this request is still active and handled by me
|
||||||
room.getTimelineEvent(action.eventId)?.let {
|
room.await().getTimelineEvent(action.eventId)?.let {
|
||||||
session.cryptoService().reRequestRoomKeyForEvent(it.root)
|
session.cryptoService().reRequestRoomKeyForEvent(it.root)
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.e2e_re_request_encryption_key_dialog_content)))
|
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.e2e_re_request_encryption_key_dialog_content)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) {
|
private suspend fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) {
|
||||||
room.getTimelineEvent(action.eventId)?.let {
|
room.await().getTimelineEvent(action.eventId)?.let {
|
||||||
val code = when (it.root.mCryptoError) {
|
val code = when (it.root.mCryptoError) {
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD -> {
|
MXCryptoError.ErrorType.KEYS_WITHHELD -> {
|
||||||
WithHeldCode.fromCode(it.root.mCryptoErrorReason)
|
WithHeldCode.fromCode(it.root.mCryptoErrorReason)
|
||||||
|
@ -1110,20 +1104,20 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) {
|
private suspend fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) {
|
||||||
// Do not allow to vote unsent local echo of the poll event
|
// Do not allow to vote unsent local echo of the poll event
|
||||||
if (LocalEcho.isLocalEchoId(action.eventId)) return
|
if (LocalEcho.isLocalEchoId(action.eventId)) return
|
||||||
// Do not allow to vote the same option twice
|
// Do not allow to vote the same option twice
|
||||||
room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
|
room.await().getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
|
||||||
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
|
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
|
||||||
if (currentVote != action.optionKey) {
|
if (currentVote != action.optionKey) {
|
||||||
room.sendService().voteToPoll(action.eventId, action.optionKey)
|
room.await().sendService().voteToPoll(action.eventId, action.optionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndPoll(eventId: String) {
|
private suspend fun handleEndPoll(eventId: String) {
|
||||||
room.sendService().endPoll(eventId)
|
room.await().sendService().endPoll(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
|
@ -1141,17 +1135,15 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStopLiveLocationSharing() {
|
private suspend fun handleStopLiveLocationSharing() {
|
||||||
viewModelScope.launch {
|
val result = stopLiveLocationShareUseCase.execute(initialState.roomId)
|
||||||
val result = stopLiveLocationShareUseCase.execute(room.roomId)
|
|
||||||
if (result is UpdateLiveLocationShareResult.Failure) {
|
if (result is UpdateLiveLocationShareResult.Failure) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.Failure(throwable = result.error, showInDialog = true))
|
_viewEvents.post(RoomDetailViewEvents.Failure(throwable = result.error, showInDialog = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private suspend fun observeRoomSummary() {
|
||||||
room.flow().liveRoomSummary()
|
room.await().flow().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(
|
copy(
|
||||||
|
@ -1160,12 +1152,14 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUnreadState() {
|
private suspend fun getUnreadState() {
|
||||||
combine(
|
combine(
|
||||||
timelineEvents,
|
timelineEvents,
|
||||||
room.flow().liveRoomSummary().unwrap()
|
room.await().flow().liveRoomSummary().unwrap()
|
||||||
) { timelineEvents, roomSummary ->
|
) { timelineEvents, roomSummary ->
|
||||||
|
withContext(coroutineDispatchers.computation) {
|
||||||
computeUnreadState(timelineEvents, roomSummary)
|
computeUnreadState(timelineEvents, roomSummary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread
|
// We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread
|
||||||
// However, we want to update an existing HasUnread, if the readMarkerId hasn't changed,
|
// However, we want to update an existing HasUnread, if the readMarkerId hasn't changed,
|
||||||
|
@ -1184,9 +1178,10 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeUnreadState(events: List<TimelineEvent>, roomSummary: RoomSummary): UnreadState {
|
private suspend fun computeUnreadState(events: List<TimelineEvent>, roomSummary: RoomSummary): UnreadState {
|
||||||
if (events.isEmpty()) return UnreadState.Unknown
|
if (events.isEmpty()) return UnreadState.Unknown
|
||||||
val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown
|
val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown
|
||||||
|
val timeline = timeline.await()
|
||||||
val firstDisplayableEventIndex = timeline.getIndexOfEvent(readMarkerIdSnapshot)
|
val firstDisplayableEventIndex = timeline.getIndexOfEvent(readMarkerIdSnapshot)
|
||||||
?: return if (timeline.isLive) {
|
?: return if (timeline.isLive) {
|
||||||
UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot)
|
UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot)
|
||||||
|
@ -1247,7 +1242,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
setState { copy(asyncInviter = Success(it)) }
|
setState { copy(asyncInviter = Success(it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also {
|
room.await().getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also {
|
||||||
setState { copy(tombstoneEvent = it) }
|
setState { copy(tombstoneEvent = it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1258,7 +1253,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
* in the snapshot. The main reason for this function is to support the /relations api
|
* in the snapshot. The main reason for this function is to support the /relations api
|
||||||
*/
|
*/
|
||||||
private var threadPermalinkHandled = false
|
private var threadPermalinkHandled = false
|
||||||
private fun navigateToThreadEventIfNeeded(snapshot: List<TimelineEvent>) {
|
private suspend fun navigateToThreadEventIfNeeded(snapshot: List<TimelineEvent>) {
|
||||||
if (eventId != null && initialState.rootThreadEventId != null) {
|
if (eventId != null && initialState.rootThreadEventId != null) {
|
||||||
// When we have a permalink and we are in a thread timeline
|
// When we have a permalink and we are in a thread timeline
|
||||||
if (snapshot.firstOrNull { it.eventId == eventId } != null && !threadPermalinkHandled) {
|
if (snapshot.firstOrNull { it.eventId == eventId } != null && !threadPermalinkHandled) {
|
||||||
|
@ -1267,7 +1262,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
threadPermalinkHandled = true
|
threadPermalinkHandled = true
|
||||||
} else {
|
} else {
|
||||||
// Permalink event not found yet continue paginating
|
// Permalink event not found yet continue paginating
|
||||||
timeline.paginate(Timeline.Direction.BACKWARDS, PAGINATION_COUNT_THREADS_PERMALINK)
|
timeline.await().paginate(Timeline.Direction.BACKWARDS, PAGINATION_COUNT_THREADS_PERMALINK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1282,9 +1277,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
override fun onTimelineFailure(throwable: Throwable) {
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
// If we have a critical timeline issue, we get back to live.
|
// If we have a critical timeline issue, we get back to live.
|
||||||
timeline.restartWithEventId(null)
|
viewModelScope.launch {
|
||||||
|
timeline.await().restartWithEventId(null)
|
||||||
_viewEvents.post(RoomDetailViewEvents.Failure(throwable))
|
_viewEvents.post(RoomDetailViewEvents.Failure(throwable))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
Timber.v("On new timeline events: $eventIds")
|
Timber.v("On new timeline events: $eventIds")
|
||||||
|
@ -1306,11 +1303,13 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
timeline.dispose()
|
decryptionFailureTracker.onTimeLineDisposed(initialState.roomId)
|
||||||
timeline.removeAllListeners()
|
session.coroutineScope.launch {
|
||||||
decryptionFailureTracker.onTimeLineDisposed(room.roomId)
|
timeline.await().dispose()
|
||||||
|
timeline.await().removeAllListeners()
|
||||||
if (vectorPreferences.sendTypingNotifs()) {
|
if (vectorPreferences.sendTypingNotifs()) {
|
||||||
room.typingService().userStopsTyping()
|
room.await().typingService().userStopsTyping()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
chatEffectManager.delegate = null
|
chatEffectManager.delegate = null
|
||||||
chatEffectManager.dispose()
|
chatEffectManager.dispose()
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
|
@ -39,9 +40,11 @@ import im.vector.app.features.home.room.detail.toMessageType
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
|
@ -71,7 +74,7 @@ import org.matrix.android.sdk.flow.unwrap
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class MessageComposerViewModel @AssistedInject constructor(
|
class MessageComposerViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: MessageComposerViewState,
|
@Assisted private val initialState: MessageComposerViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
@ -79,20 +82,26 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
private val rainbowGenerator: RainbowGenerator,
|
private val rainbowGenerator: RainbowGenerator,
|
||||||
private val audioMessageHelper: AudioMessageHelper,
|
private val audioMessageHelper: AudioMessageHelper,
|
||||||
private val analyticsTracker: AnalyticsTracker,
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = viewModelScope.async(start = CoroutineStart.LAZY) {
|
||||||
|
session.roomService().awaitRoom(initialState.roomId)!!
|
||||||
|
}
|
||||||
|
|
||||||
// Keep it out of state to avoid invalidate being called
|
// Keep it out of state to avoid invalidate being called
|
||||||
private var currentComposerText: CharSequence = ""
|
private var currentComposerText: CharSequence = ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
loadDraftIfAny()
|
loadDraftIfAny()
|
||||||
observePowerLevelAndEncryption()
|
observePowerLevelAndEncryption()
|
||||||
|
}
|
||||||
subscribeToStateInternal()
|
subscribeToStateInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: MessageComposerAction) {
|
override fun handle(action: MessageComposerAction) {
|
||||||
|
viewModelScope.launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action)
|
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action)
|
||||||
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
|
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
|
||||||
|
@ -116,6 +125,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
|
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
|
private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
|
||||||
copy(voiceRecordingUiState = action.uiState)
|
copy(voiceRecordingUiState = action.uiState)
|
||||||
|
@ -148,13 +158,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
copy(sendMode = SendMode.Regular(action.text, action.fromSharing))
|
copy(sendMode = SendMode.Regular(action.text, action.fromSharing))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
|
private suspend fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent())) }
|
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent())) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observePowerLevelAndEncryption() {
|
private suspend fun observePowerLevelAndEncryption() {
|
||||||
|
val room = room.await()
|
||||||
combine(
|
combine(
|
||||||
PowerLevelsFlowFactory(room).createFlow(),
|
PowerLevelsFlowFactory(room).createFlow(),
|
||||||
room.flow().liveRoomSummary().unwrap()
|
room.flow().liveRoomSummary().unwrap()
|
||||||
|
@ -180,23 +191,22 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
|
private suspend fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Quote(timelineEvent, action.text)) }
|
setState { copy(sendMode = SendMode.Quote(timelineEvent, action.text)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
|
private suspend fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Reply(timelineEvent, action.text)) }
|
setState { copy(sendMode = SendMode.Reply(timelineEvent, action.text)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
|
private suspend fun handleSendMessage(action: MessageComposerAction.SendMessage) {
|
||||||
withState { state ->
|
val state = awaitState()
|
||||||
analyticsTracker.capture(state.toAnalyticsComposer()).also {
|
analyticsTracker.capture(state.toAnalyticsComposer())
|
||||||
setState { copy(startsThread = false) }
|
setState { copy(startsThread = false) }
|
||||||
}
|
|
||||||
when (state.sendMode) {
|
when (state.sendMode) {
|
||||||
is SendMode.Regular -> {
|
is SendMode.Regular -> {
|
||||||
when (val parsedCommand = commandParser.parseSlashCommand(
|
when (val parsedCommand = commandParser.parseSlashCommand(
|
||||||
|
@ -206,13 +216,13 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.ErrorNotACommand -> {
|
is ParsedCommand.ErrorNotACommand -> {
|
||||||
// Send the text message to the room
|
// Send the text message to the room
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = action.text,
|
replyInThreadText = action.text,
|
||||||
autoMarkdown = action.autoMarkdown
|
autoMarkdown = action.autoMarkdown
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
room.await().sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
|
@ -233,13 +243,13 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.SendPlainText -> {
|
is ParsedCommand.SendPlainText -> {
|
||||||
// Send the text message to the room, without markdown
|
// Send the text message to the room, without markdown
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = parsedCommand.message,
|
replyInThreadText = parsedCommand.message,
|
||||||
autoMarkdown = false
|
autoMarkdown = false
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
room.await().sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -289,14 +299,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendEmote -> {
|
is ParsedCommand.SendEmote -> {
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = parsedCommand.message,
|
replyInThreadText = parsedCommand.message,
|
||||||
msgType = MessageType.MSGTYPE_EMOTE,
|
msgType = MessageType.MSGTYPE_EMOTE,
|
||||||
autoMarkdown = action.autoMarkdown
|
autoMarkdown = action.autoMarkdown
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendTextMessage(
|
room.await().sendService().sendTextMessage(
|
||||||
text = parsedCommand.message,
|
text = parsedCommand.message,
|
||||||
msgType = MessageType.MSGTYPE_EMOTE,
|
msgType = MessageType.MSGTYPE_EMOTE,
|
||||||
autoMarkdown = action.autoMarkdown
|
autoMarkdown = action.autoMarkdown
|
||||||
|
@ -308,13 +318,13 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.SendRainbow -> {
|
is ParsedCommand.SendRainbow -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = parsedCommand.message,
|
replyInThreadText = parsedCommand.message,
|
||||||
formattedText = rainbowGenerator.generate(message)
|
formattedText = rainbowGenerator.generate(message)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
room.await().sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -322,14 +332,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.SendRainbowEmote -> {
|
is ParsedCommand.SendRainbowEmote -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = parsedCommand.message,
|
replyInThreadText = parsedCommand.message,
|
||||||
msgType = MessageType.MSGTYPE_EMOTE,
|
msgType = MessageType.MSGTYPE_EMOTE,
|
||||||
formattedText = rainbowGenerator.generate(message)
|
formattedText = rainbowGenerator.generate(message)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE)
|
room.await().sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE)
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
|
@ -339,13 +349,13 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
||||||
val formattedText = "<span data-mx-spoiler>${parsedCommand.message}</span>"
|
val formattedText = "<span data-mx-spoiler>${parsedCommand.message}</span>"
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = state.rootThreadEventId,
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
replyInThreadText = text,
|
replyInThreadText = text,
|
||||||
formattedText = formattedText
|
formattedText = formattedText
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendFormattedTextMessage(
|
room.await().sendService().sendFormattedTextMessage(
|
||||||
text,
|
text,
|
||||||
formattedText
|
formattedText
|
||||||
)
|
)
|
||||||
|
@ -389,8 +399,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
is ParsedCommand.DiscardSession -> {
|
is ParsedCommand.DiscardSession -> {
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.await().roomCryptoService().isEncrypted()) {
|
||||||
session.cryptoService().discardOutboundSession(room.roomId)
|
session.cryptoService().discardOutboundSession(room.await().roomId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft()
|
||||||
} else {
|
} else {
|
||||||
|
@ -403,7 +413,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is ParsedCommand.CreateSpace -> {
|
is ParsedCommand.CreateSpace -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
val params = CreateSpaceParams().apply {
|
val params = CreateSpaceParams().apply {
|
||||||
name = parsedCommand.name
|
name = parsedCommand.name
|
||||||
|
@ -423,15 +433,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is ParsedCommand.AddToSpace -> {
|
is ParsedCommand.AddToSpace -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
session.spaceService().getSpace(parsedCommand.spaceId)
|
session.spaceService().getSpace(parsedCommand.spaceId)
|
||||||
?.addChildren(
|
?.addChildren(
|
||||||
room.roomId,
|
room.await().roomId,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
false
|
false
|
||||||
|
@ -442,11 +451,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinSpace -> {
|
is ParsedCommand.JoinSpace -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -455,10 +463,9 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is ParsedCommand.LeaveRoom -> {
|
is ParsedCommand.LeaveRoom -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
session.roomService().leaveRoom(parsedCommand.roomId)
|
session.roomService().leaveRoom(parsedCommand.roomId)
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -467,13 +474,12 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is ParsedCommand.UpgradeRoom -> {
|
is ParsedCommand.UpgradeRoom -> {
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
MessageComposerViewEvents.ShowRoomUpgradeDialog(
|
MessageComposerViewEvents.ShowRoomUpgradeDialog(
|
||||||
parsedCommand.newVersion,
|
parsedCommand.newVersion,
|
||||||
room.roomSummary()?.isPublic ?: false
|
room.await().awaitRoomSummary()?.isPublic ?: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
|
@ -500,14 +506,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
if (inReplyTo != null) {
|
if (inReplyTo != null) {
|
||||||
// TODO check if same content?
|
// TODO check if same content?
|
||||||
room.getTimelineEvent(inReplyTo)?.let {
|
room.await().getTimelineEvent(inReplyTo)?.let {
|
||||||
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
|
room.await().relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
|
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
|
||||||
val existingBody = messageContent?.body ?: ""
|
val existingBody = messageContent?.body ?: ""
|
||||||
if (existingBody != action.text) {
|
if (existingBody != action.text) {
|
||||||
room.relationService().editTextMessage(
|
room.await().relationService().editTextMessage(
|
||||||
state.sendMode.timelineEvent,
|
state.sendMode.timelineEvent,
|
||||||
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
|
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
|
||||||
action.text,
|
action.text,
|
||||||
|
@ -521,7 +527,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
is SendMode.Quote -> {
|
is SendMode.Quote -> {
|
||||||
room.sendService().sendQuotedTextMessage(
|
room.await().sendService().sendQuotedTextMessage(
|
||||||
quotedEvent = state.sendMode.timelineEvent,
|
quotedEvent = state.sendMode.timelineEvent,
|
||||||
text = action.text.toString(),
|
text = action.text.toString(),
|
||||||
autoMarkdown = action.autoMarkdown,
|
autoMarkdown = action.autoMarkdown,
|
||||||
|
@ -536,13 +542,13 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
// If threads are disabled this will make the fallback replies visible to clients with threads enabled
|
// If threads are disabled this will make the fallback replies visible to clients with threads enabled
|
||||||
val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null
|
val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null
|
||||||
state.rootThreadEventId?.let {
|
state.rootThreadEventId?.let {
|
||||||
room.relationService().replyInThread(
|
room.await().relationService().replyInThread(
|
||||||
rootThreadEventId = it,
|
rootThreadEventId = it,
|
||||||
replyInThreadText = action.text.toString(),
|
replyInThreadText = action.text.toString(),
|
||||||
autoMarkdown = action.autoMarkdown,
|
autoMarkdown = action.autoMarkdown,
|
||||||
eventReplied = timelineEvent
|
eventReplied = timelineEvent
|
||||||
)
|
)
|
||||||
} ?: room.relationService().replyToMessage(
|
} ?: room.await().relationService().replyToMessage(
|
||||||
eventReplied = timelineEvent,
|
eventReplied = timelineEvent,
|
||||||
replyText = action.text.toString(),
|
replyText = action.text.toString(),
|
||||||
autoMarkdown = action.autoMarkdown,
|
autoMarkdown = action.autoMarkdown,
|
||||||
|
@ -558,61 +564,62 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun popDraft() = withState {
|
private suspend fun popDraft() = withContext(coroutineDispatchers.computation) {
|
||||||
if (it.sendMode is SendMode.Regular && it.sendMode.fromSharing) {
|
val state = awaitState()
|
||||||
|
if (state.sendMode is SendMode.Regular && state.sendMode.fromSharing) {
|
||||||
// If we were sharing, we want to get back our last value from draft
|
// If we were sharing, we want to get back our last value from draft
|
||||||
loadDraftIfAny()
|
loadDraftIfAny()
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we clear the composer and remove the draft from db
|
// Otherwise we clear the composer and remove the draft from db
|
||||||
setState { copy(sendMode = SendMode.Regular("", false)) }
|
setState { copy(sendMode = SendMode.Regular("", false)) }
|
||||||
viewModelScope.launch {
|
room.await().draftService().deleteDraft()
|
||||||
room.draftService().deleteDraft()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDraftIfAny() {
|
private suspend fun loadDraftIfAny() {
|
||||||
val currentDraft = room.draftService().getDraft()
|
val currentDraft = withContext(coroutineDispatchers.computation) {
|
||||||
setState {
|
room.await().draftService().getDraft()
|
||||||
copy(
|
}
|
||||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
val sendMode = when (currentDraft) {
|
||||||
sendMode = when (currentDraft) {
|
|
||||||
is UserDraft.Regular -> SendMode.Regular(currentDraft.content, false)
|
is UserDraft.Regular -> SendMode.Regular(currentDraft.content, false)
|
||||||
is UserDraft.Quote -> {
|
is UserDraft.Quote -> {
|
||||||
room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.Quote(timelineEvent, currentDraft.content)
|
SendMode.Quote(timelineEvent, currentDraft.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserDraft.Reply -> {
|
is UserDraft.Reply -> {
|
||||||
room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.Reply(timelineEvent, currentDraft.content)
|
SendMode.Reply(timelineEvent, currentDraft.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserDraft.Edit -> {
|
is UserDraft.Edit -> {
|
||||||
room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
room.await().getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.Edit(timelineEvent, currentDraft.content)
|
SendMode.Edit(timelineEvent, currentDraft.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserDraft.Voice -> SendMode.Voice(currentDraft.content)
|
is UserDraft.Voice -> SendMode.Voice(currentDraft.content)
|
||||||
else -> null
|
else -> null
|
||||||
} ?: SendMode.Regular("", fromSharing = false)
|
} ?: SendMode.Regular("", fromSharing = false)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||||
|
sendMode = sendMode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
|
private suspend fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
|
||||||
if (vectorPreferences.sendTypingNotifs()) {
|
if (vectorPreferences.sendTypingNotifs()) {
|
||||||
if (action.isTyping) {
|
if (action.isTyping) {
|
||||||
room.typingService().userIsTyping()
|
room.await().typingService().userIsTyping()
|
||||||
} else {
|
} else {
|
||||||
room.typingService().userStopsTyping()
|
room.await().typingService().userStopsTyping()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendChatEffect(sendChatEffect: ParsedCommand.SendChatEffect) {
|
private suspend fun sendChatEffect(sendChatEffect: ParsedCommand.SendChatEffect) {
|
||||||
// If message is blank, convert to an emote, with default message
|
// If message is blank, convert to an emote, with default message
|
||||||
if (sendChatEffect.message.isBlank()) {
|
if (sendChatEffect.message.isBlank()) {
|
||||||
val defaultMessage = stringProvider.getString(
|
val defaultMessage = stringProvider.getString(
|
||||||
|
@ -621,19 +628,18 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
ChatEffect.SNOWFALL -> R.string.default_message_emote_snow
|
ChatEffect.SNOWFALL -> R.string.default_message_emote_snow
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
room.sendService().sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
|
room.await().sendService().sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
|
||||||
} else {
|
} else {
|
||||||
room.sendService().sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType())
|
room.await().sendService().sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
|
private suspend fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
try {
|
||||||
session.roomService().joinRoom(command.roomAlias, command.reason, emptyList())
|
session.roomService().joinRoom(command.roomAlias, command.reason, emptyList())
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
return@launch
|
return
|
||||||
}
|
}
|
||||||
session.getRoomSummary(command.roomAlias)
|
session.getRoomSummary(command.roomAlias)
|
||||||
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SlashCommand)) }
|
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SlashCommand)) }
|
||||||
|
@ -642,7 +648,6 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
|
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
|
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
|
||||||
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
|
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
|
||||||
|
@ -664,26 +669,26 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
private suspend fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||||
launchSlashCommandFlowSuspendable(changeTopic) {
|
launchSlashCommandFlowSuspendable(changeTopic) {
|
||||||
room.stateService().updateTopic(changeTopic.topic)
|
room.await().stateService().updateTopic(changeTopic.topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
private suspend fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(invite) {
|
||||||
room.membershipService().invite(invite.userId, invite.reason)
|
room.await().membershipService().invite(invite.userId, invite.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
|
private suspend fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(invite) {
|
||||||
room.membershipService().invite3pid(invite.threePid)
|
room.await().membershipService().invite3pid(invite.threePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
private suspend fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
||||||
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
val newPowerLevelsContent = room.await().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||||
?.content
|
?.content
|
||||||
?.toModel<PowerLevelsContent>()
|
?.toModel<PowerLevelsContent>()
|
||||||
?.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel)
|
?.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel)
|
||||||
|
@ -691,21 +696,21 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
launchSlashCommandFlowSuspendable(setUserPowerLevel) {
|
launchSlashCommandFlowSuspendable(setUserPowerLevel) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
room.await().stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
private suspend fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
||||||
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
|
private suspend fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
|
||||||
launchSlashCommandFlowSuspendable(command) {
|
launchSlashCommandFlowSuspendable(command) {
|
||||||
if (command.roomAlias == null) {
|
if (command.roomAlias == null) {
|
||||||
// Leave the current room
|
// Leave the current room
|
||||||
room
|
room.await()
|
||||||
} else {
|
} else {
|
||||||
session.getRoomSummary(roomIdOrAlias = command.roomAlias)
|
session.getRoomSummary(roomIdOrAlias = command.roomAlias)
|
||||||
?.roomId
|
?.roomId
|
||||||
|
@ -717,65 +722,65 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
|
private suspend fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
|
||||||
launchSlashCommandFlowSuspendable(removeUser) {
|
launchSlashCommandFlowSuspendable(removeUser) {
|
||||||
room.membershipService().remove(removeUser.userId, removeUser.reason)
|
room.await().membershipService().remove(removeUser.userId, removeUser.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
|
private suspend fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
|
||||||
launchSlashCommandFlowSuspendable(ban) {
|
launchSlashCommandFlowSuspendable(ban) {
|
||||||
room.membershipService().ban(ban.userId, ban.reason)
|
room.await().membershipService().ban(ban.userId, ban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
|
private suspend fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
|
||||||
launchSlashCommandFlowSuspendable(unban) {
|
launchSlashCommandFlowSuspendable(unban) {
|
||||||
room.membershipService().unban(unban.userId, unban.reason)
|
room.await().membershipService().unban(unban.userId, unban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
|
private suspend fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
|
||||||
launchSlashCommandFlowSuspendable(changeRoomName) {
|
launchSlashCommandFlowSuspendable(changeRoomName) {
|
||||||
room.stateService().updateName(changeRoomName.name)
|
room.await().stateService().updateName(changeRoomName.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMyRoomMemberContent(): RoomMemberContent? {
|
private suspend fun getMyRoomMemberContent(): RoomMemberContent? {
|
||||||
return room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
return room.await().getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
||||||
?.content
|
?.content
|
||||||
?.toModel<RoomMemberContent>()
|
?.toModel<RoomMemberContent>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
private suspend fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent()
|
||||||
?.copy(displayName = changeDisplayName.displayName)
|
?.copy(displayName = changeDisplayName.displayName)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
|
room.await().stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
private suspend fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(changeAvatar) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
room.await().stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
private suspend fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(changeAvatar) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent()
|
||||||
?.copy(avatarUrl = changeAvatar.url)
|
?.copy(avatarUrl = changeAvatar.url)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
|
room.await().stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) {
|
private suspend fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(ignore) {
|
launchSlashCommandFlowSuspendable(ignore) {
|
||||||
session.userService().ignoreUserIds(listOf(ignore.userId))
|
session.userService().ignoreUserIds(listOf(ignore.userId))
|
||||||
}
|
}
|
||||||
|
@ -785,14 +790,14 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) {
|
private suspend fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) {
|
||||||
when (action.parsedCommand) {
|
when (action.parsedCommand) {
|
||||||
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand)
|
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand)
|
||||||
else -> TODO("Not handled yet")
|
else -> TODO("Not handled yet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) {
|
private suspend fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(unignore) {
|
launchSlashCommandFlowSuspendable(unignore) {
|
||||||
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
||||||
}
|
}
|
||||||
|
@ -802,7 +807,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendPrefixedMessage(prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
private suspend fun sendPrefixedMessage(prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
||||||
val sequence = buildString {
|
val sequence = buildString {
|
||||||
append(prefix)
|
append(prefix)
|
||||||
if (message.isNotEmpty()) {
|
if (message.isNotEmpty()) {
|
||||||
|
@ -811,52 +816,52 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootThreadEventId?.let {
|
rootThreadEventId?.let {
|
||||||
room.relationService().replyInThread(it, sequence)
|
room.await().relationService().replyInThread(it, sequence)
|
||||||
} ?: room.sendService().sendTextMessage(sequence)
|
} ?: room.await().sendService().sendTextMessage(sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a send mode to a draft and save the draft.
|
* Convert a send mode to a draft and save the draft.
|
||||||
*/
|
*/
|
||||||
private fun handleSaveTextDraft(draft: String) = withState {
|
private suspend fun handleSaveTextDraft(draft: String) = withState {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
when {
|
when {
|
||||||
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
||||||
room.draftService().saveDraft(UserDraft.Regular(draft))
|
room.await().draftService().saveDraft(UserDraft.Regular(draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.Reply -> {
|
it.sendMode is SendMode.Reply -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
||||||
room.draftService().saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft))
|
room.await().draftService().saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.Quote -> {
|
it.sendMode is SendMode.Quote -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
||||||
room.draftService().saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft))
|
room.await().draftService().saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.Edit -> {
|
it.sendMode is SendMode.Edit -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
|
||||||
room.draftService().saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft))
|
room.await().draftService().saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartRecordingVoiceMessage() {
|
private suspend fun handleStartRecordingVoiceMessage() {
|
||||||
try {
|
try {
|
||||||
audioMessageHelper.startRecording(room.roomId)
|
audioMessageHelper.startRecording(room.await().roomId)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
|
_viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
|
private suspend fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
|
||||||
audioMessageHelper.stopPlayback()
|
audioMessageHelper.stopPlayback()
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
audioMessageHelper.deleteRecording()
|
audioMessageHelper.deleteRecording()
|
||||||
} else {
|
} else {
|
||||||
audioMessageHelper.stopRecording()?.let { audioType ->
|
audioMessageHelper.stopRecording()?.let { audioType ->
|
||||||
if (audioType.duration > 1000) {
|
if (audioType.duration > 1000) {
|
||||||
room.sendService().sendMedia(
|
room.await().sendService().sendMedia(
|
||||||
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
|
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
|
||||||
compressBeforeSending = false,
|
compressBeforeSending = false,
|
||||||
roomIds = emptySet(),
|
roomIds = emptySet(),
|
||||||
|
@ -870,8 +875,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
handleEnterRegularMode(MessageComposerAction.EnterRegularMode(text = "", fromSharing = false))
|
handleEnterRegularMode(MessageComposerAction.EnterRegularMode(text = "", fromSharing = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePlayOrPauseVoicePlayback(action: MessageComposerAction.PlayOrPauseVoicePlayback) {
|
private suspend fun handlePlayOrPauseVoicePlayback(action: MessageComposerAction.PlayOrPauseVoicePlayback) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
try {
|
try {
|
||||||
// Download can fail
|
// Download can fail
|
||||||
val audioFile = session.fileService().downloadFile(action.messageAudioContent)
|
val audioFile = session.fileService().downloadFile(action.messageAudioContent)
|
||||||
|
@ -913,16 +918,15 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEntersBackground(composerText: String) {
|
private suspend fun handleEntersBackground(composerText: String) {
|
||||||
// Always stop all voice actions. It may be playing in timeline or active recording
|
// Always stop all voice actions. It may be playing in timeline or active recording
|
||||||
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
||||||
|
val isVoiceRecording = awaitState().isVoiceRecording
|
||||||
val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
|
|
||||||
if (isVoiceRecording) {
|
if (isVoiceRecording) {
|
||||||
viewModelScope.launch {
|
withContext(coroutineDispatchers.io) {
|
||||||
playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
|
playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
|
||||||
val content = voiceDraft.toJsonString()
|
val content = voiceDraft.toJsonString()
|
||||||
room.draftService().saveDraft(UserDraft.Voice(content))
|
room.await().draftService().saveDraft(UserDraft.Voice(content))
|
||||||
setState { copy(sendMode = SendMode.Voice(content)) }
|
setState { copy(sendMode = SendMode.Voice(content)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -931,9 +935,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
private suspend fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch {
|
|
||||||
val event = try {
|
val event = try {
|
||||||
block()
|
block()
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -943,7 +946,6 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
_viewEvents.post(event)
|
_viewEvents.post(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.app.features.call.vectorCallService
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines
|
import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
@ -36,14 +37,14 @@ private val secondaryTimelineAllowedTypes = listOf(
|
||||||
|
|
||||||
class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) {
|
class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) {
|
||||||
|
|
||||||
fun createTimeline(
|
suspend fun createTimeline(
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
mainRoom: Room,
|
room: Deferred<Room>,
|
||||||
eventId: String?,
|
eventId: String?,
|
||||||
rootThreadEventId: String?
|
rootThreadEventId: String?
|
||||||
): Timeline {
|
): Timeline {
|
||||||
val settings = timelineSettingsFactory.create(rootThreadEventId)
|
val settings = timelineSettingsFactory.create(rootThreadEventId)
|
||||||
|
val mainRoom = room.await()
|
||||||
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
|
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
|
||||||
return mainRoom.timelineService().createTimeline(eventId, settings)
|
return mainRoom.timelineService().createTimeline(eventId, settings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,7 +294,7 @@ class RoomListViewModel @AssistedInject constructor(
|
||||||
session.getRoom(action.roomId)?.let { room ->
|
session.getRoom(action.roomId)?.let { room ->
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (room.roomSummary()?.hasTag(action.tag) == false) {
|
if (room.awaitRoomSummary()?.hasTag(action.tag) == false) {
|
||||||
// Favorite and low priority tags are exclusive, so maybe delete the other tag first
|
// Favorite and low priority tags are exclusive, so maybe delete the other tag first
|
||||||
action.tag.otherTag()
|
action.tag.otherTag()
|
||||||
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
|
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
|
||||||
|
|
|
@ -122,7 +122,7 @@ class InvitesAcceptor @Inject constructor(
|
||||||
// if we got 404 on invites, the inviting user have left or the hs is off.
|
// if we got 404 on invites, the inviting user have left or the hs is off.
|
||||||
if (failure is Failure.ServerError && failure.httpCode == 404) {
|
if (failure is Failure.ServerError && failure.httpCode == 404) {
|
||||||
val room = getRoom(roomId) ?: return
|
val room = getRoom(roomId) ?: return
|
||||||
val inviterId = room.roomSummary()?.inviterId
|
val inviterId = room.awaitRoomSummary()?.inviterId
|
||||||
// if the inviting user is on the same HS, there can only be one cause: they left, so we try to reject the invite.
|
// if the inviting user is on the same HS, there can only be one cause: they left, so we try to reject the invite.
|
||||||
if (inviterId?.endsWith(sessionParams.credentials.homeServer.orEmpty()).orFalse()) {
|
if (inviterId?.endsWith(sessionParams.credentials.homeServer.orEmpty()).orFalse()) {
|
||||||
shouldRejectRoomIds.add(roomId)
|
shouldRejectRoomIds.add(roomId)
|
||||||
|
|
|
@ -47,7 +47,7 @@ fun ElementWellKnown?.getOutboundSessionKeySharingStrategyOrDefault(): OutboundS
|
||||||
fun RawService.withElementWellKnown(
|
fun RawService.withElementWellKnown(
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
sessionParams: SessionParams,
|
sessionParams: SessionParams,
|
||||||
block: ((ElementWellKnown?) -> Unit)
|
block: suspend ((ElementWellKnown?) -> Unit)
|
||||||
) = with(coroutineScope) {
|
) = with(coroutineScope) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
block(getElementWellknown(sessionParams))
|
block(getElementWellknown(sessionParams))
|
||||||
|
|
Loading…
Reference in New Issue