mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #2158 from vector-im/feature/fix_draft
Feature/fix draft
This commit is contained in:
commit
cac3475281
@ -16,6 +16,7 @@ Improvements 🙌:
|
||||
Bugfix 🐛:
|
||||
- Improve support for image/audio/video/file selection with intent changes (#1376)
|
||||
- Fix Splash layout on small screens
|
||||
- Simplifies draft management and should fix bunch of draft issues (#952, #683)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||
return room.getDraftsLive().asObservable()
|
||||
fun liveDraft(): Observable<Optional<UserDraft>> {
|
||||
return room.getDraftLive().asObservable()
|
||||
.startWithCallable {
|
||||
room.getDraft().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
|
@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
interface DraftService {
|
||||
|
||||
@ -34,8 +35,12 @@ interface DraftService {
|
||||
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Return the current drafts if any, as a live data
|
||||
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
|
||||
* Return the current draft or null
|
||||
*/
|
||||
fun getDraftsLive(): LiveData<List<UserDraft>>
|
||||
fun getDraft(): UserDraft?
|
||||
|
||||
/**
|
||||
* Return the current draft if any, as a live data
|
||||
*/
|
||||
fun getDraftLive(): LiveData<Optional<UserDraft>>
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.room.send.DraftService
|
||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDraftsLive(): LiveData<List<UserDraft>> {
|
||||
override fun getDraft(): UserDraft? {
|
||||
return draftRepository.getDraft(roomId)
|
||||
}
|
||||
|
||||
override fun getDraftLive(): LiveData<Optional<UserDraft>> {
|
||||
return draftRepository.getDraftsLive(roomId)
|
||||
}
|
||||
}
|
||||
|
@ -20,43 +20,67 @@ package org.matrix.android.sdk.internal.session.room.draft
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.DraftMapper
|
||||
import org.matrix.android.sdk.internal.database.model.DraftEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider) {
|
||||
|
||||
suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
|
||||
monarchy.awaitTransaction {
|
||||
saveDraft(it, userDraft, roomId)
|
||||
saveDraftInDb(it, userDraft, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteDraft(roomId: String) {
|
||||
monarchy.awaitTransaction {
|
||||
deleteDraft(it, roomId)
|
||||
deleteDraftFromDb(it, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDraft(realm: Realm, roomId: String) {
|
||||
UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity ->
|
||||
if (userDraftsEntity.userDrafts.isNotEmpty()) {
|
||||
userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1)
|
||||
}
|
||||
fun getDraft(roomId: String): UserDraft? {
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
UserDraftsEntity.where(realm, roomId).findFirst()
|
||||
?.userDrafts
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
DraftMapper.map(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveDraft(realm: Realm, draft: UserDraft, roomId: String) {
|
||||
fun getDraftsLive(roomId: String): LiveData<Optional<UserDraft>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ UserDraftsEntity.where(it, roomId) },
|
||||
{
|
||||
it.userDrafts.map { draft ->
|
||||
DraftMapper.map(draft)
|
||||
}
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull()?.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDraftFromDb(realm: Realm, roomId: String) {
|
||||
UserDraftsEntity.where(realm, roomId).findFirst()?.userDrafts?.clear()
|
||||
}
|
||||
|
||||
private fun saveDraftInDb(realm: Realm, draft: UserDraft, roomId: String) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
?: realm.createObject(roomId)
|
||||
|
||||
@ -68,62 +92,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
|
||||
userDraftsEntity.let { userDraftEntity ->
|
||||
// Save only valid draft
|
||||
if (draft.isValid()) {
|
||||
// Add a new draft or update the current one?
|
||||
// Replace the current draft
|
||||
val newDraft = DraftMapper.map(draft)
|
||||
|
||||
// Is it an update of the top draft?
|
||||
val topDraft = userDraftEntity.userDrafts.lastOrNull()
|
||||
|
||||
if (topDraft == null) {
|
||||
Timber.d("Draft: create a new draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.add(newDraft)
|
||||
} else if (topDraft.draftMode == DraftEntity.MODE_EDIT) {
|
||||
// top draft is an edit
|
||||
if (newDraft.draftMode == DraftEntity.MODE_EDIT) {
|
||||
if (topDraft.linkedEventId == newDraft.linkedEventId) {
|
||||
// Update the top draft
|
||||
Timber.d("Draft: update the top edit draft ${privacySafe(draft)}")
|
||||
topDraft.content = newDraft.content
|
||||
} else {
|
||||
// Check a previously EDIT draft with the same id
|
||||
val existingEditDraftOfSameEvent = userDraftEntity.userDrafts.find {
|
||||
it.draftMode == DraftEntity.MODE_EDIT && it.linkedEventId == newDraft.linkedEventId
|
||||
}
|
||||
|
||||
if (existingEditDraftOfSameEvent != null) {
|
||||
// Ignore the new text, restore what was typed before, by putting the draft to the top
|
||||
Timber.d("Draft: restore a previously edit draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.remove(existingEditDraftOfSameEvent)
|
||||
userDraftEntity.userDrafts.add(existingEditDraftOfSameEvent)
|
||||
} else {
|
||||
Timber.d("Draft: add a new edit draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.add(newDraft)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add a new regular draft to the top
|
||||
Timber.d("Draft: add a new draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.add(newDraft)
|
||||
}
|
||||
} else {
|
||||
// Top draft is not an edit
|
||||
if (newDraft.draftMode == DraftEntity.MODE_EDIT) {
|
||||
Timber.d("Draft: create a new edit draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.add(newDraft)
|
||||
} else {
|
||||
// Update the top draft
|
||||
Timber.d("Draft: update the top draft ${privacySafe(draft)}")
|
||||
topDraft.draftMode = newDraft.draftMode
|
||||
topDraft.content = newDraft.content
|
||||
topDraft.linkedEventId = newDraft.linkedEventId
|
||||
}
|
||||
}
|
||||
Timber.d("Draft: create a new draft ${privacySafe(draft)}")
|
||||
userDraftEntity.userDrafts.clear()
|
||||
userDraftEntity.userDrafts.add(newDraft)
|
||||
} else {
|
||||
// There is no draft to save, so the composer was clear
|
||||
Timber.d("Draft: delete a draft")
|
||||
|
||||
val topDraft = userDraftEntity.userDrafts.lastOrNull()
|
||||
|
||||
if (topDraft == null) {
|
||||
Timber.d("Draft: nothing to do")
|
||||
} else {
|
||||
@ -135,20 +112,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
|
||||
}
|
||||
}
|
||||
|
||||
fun getDraftsLive(roomId: String): LiveData<List<UserDraft>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ UserDraftsEntity.where(it, roomId) },
|
||||
{
|
||||
it.userDrafts.map { draft ->
|
||||
DraftMapper.map(draft)
|
||||
}
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull().orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun privacySafe(o: Any): Any {
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
return o
|
||||
|
@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||
data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
|
||||
data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction()
|
||||
data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction()
|
||||
data class ExitSpecialMode(val text: String) : RoomDetailAction()
|
||||
data class EnterRegularMode(val text: String, val fromSharing: Boolean) : RoomDetailAction()
|
||||
|
||||
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
||||
|
@ -485,8 +485,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
if (savedInstanceState == null) {
|
||||
when (val sharedData = roomDetailArgs.sharedData) {
|
||||
is SharedData.Text -> {
|
||||
// Save a draft to set the shared text to the composer
|
||||
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text))
|
||||
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
|
||||
}
|
||||
is SharedData.Attachments -> {
|
||||
// open share edition
|
||||
@ -1014,7 +1013,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onCloseRelatedMessage() {
|
||||
roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString()))
|
||||
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false))
|
||||
}
|
||||
|
||||
override fun onRichContentSelected(contentUri: Uri): Boolean {
|
||||
@ -1147,12 +1146,8 @@ class RoomDetailFragment @Inject constructor(
|
||||
|
||||
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
|
||||
when (sendMessageResult) {
|
||||
is RoomDetailViewEvents.MessageSent -> {
|
||||
updateComposerText("")
|
||||
}
|
||||
is RoomDetailViewEvents.SlashCommandHandled -> {
|
||||
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
|
||||
updateComposerText("")
|
||||
}
|
||||
is RoomDetailViewEvents.SlashCommandError -> {
|
||||
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
|
||||
|
@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
getUnreadState()
|
||||
observeSyncState()
|
||||
observeEventDisplayedActions()
|
||||
observeDrafts()
|
||||
getDraftIfAny()
|
||||
observeUnreadState()
|
||||
observeMyRoomMember()
|
||||
observeActiveRoomWidgets()
|
||||
@ -226,52 +226,52 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
|
||||
override fun handle(action: RoomDetailAction) {
|
||||
when (action) {
|
||||
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
||||
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
||||
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
||||
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
||||
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
||||
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||
is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action)
|
||||
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
||||
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
||||
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
||||
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
||||
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||
is RoomDetailAction.ClearSendQueue -> handleClearSendQueue()
|
||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
||||
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
||||
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
||||
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
||||
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
||||
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
||||
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
||||
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
||||
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
||||
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||
is RoomDetailAction.ClearSendQueue -> handleClearSendQueue()
|
||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@ -449,47 +449,52 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
/**
|
||||
* Convert a send mode to a draft and save the draft
|
||||
*/
|
||||
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) {
|
||||
withState {
|
||||
when (it.sendMode) {
|
||||
is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
|
||||
is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
}.exhaustive
|
||||
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
|
||||
when {
|
||||
it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(action.draft)) }
|
||||
room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
|
||||
}
|
||||
it.sendMode is SendMode.REPLY -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
}
|
||||
it.sendMode is SendMode.QUOTE -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
}
|
||||
it.sendMode is SendMode.EDIT -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeDrafts() {
|
||||
room.rx().liveDrafts()
|
||||
.subscribe {
|
||||
Timber.d("Draft update --> SetState")
|
||||
setState {
|
||||
val draft = it.lastOrNull() ?: UserDraft.REGULAR("")
|
||||
copy(
|
||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||
sendMode = when (draft) {
|
||||
is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
|
||||
is UserDraft.QUOTE -> {
|
||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.QUOTE(timelineEvent, draft.text)
|
||||
}
|
||||
}
|
||||
is UserDraft.REPLY -> {
|
||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.REPLY(timelineEvent, draft.text)
|
||||
}
|
||||
}
|
||||
is UserDraft.EDIT -> {
|
||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.EDIT(timelineEvent, draft.text)
|
||||
}
|
||||
}
|
||||
} ?: SendMode.REGULAR("")
|
||||
)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
private fun getDraftIfAny() {
|
||||
val currentDraft = room.getDraft() ?: return
|
||||
setState {
|
||||
copy(
|
||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||
sendMode = when (currentDraft) {
|
||||
is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
|
||||
is UserDraft.QUOTE -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.QUOTE(timelineEvent, currentDraft.text)
|
||||
}
|
||||
}
|
||||
is UserDraft.REPLY -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.REPLY(timelineEvent, currentDraft.text)
|
||||
}
|
||||
}
|
||||
is UserDraft.EDIT -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.EDIT(timelineEvent, currentDraft.text)
|
||||
}
|
||||
}
|
||||
} ?: SendMode.REGULAR("", fromSharing = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) {
|
||||
@ -680,7 +685,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
is SendMode.EDIT -> {
|
||||
// is original event a reply?
|
||||
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
@ -706,7 +711,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.QUOTE -> {
|
||||
is SendMode.QUOTE -> {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
@ -729,7 +734,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.REPLY -> {
|
||||
is SendMode.REPLY -> {
|
||||
state.sendMode.timelineEvent.let {
|
||||
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
@ -740,8 +745,15 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun popDraft() {
|
||||
room.deleteDraft(NoOpMatrixCallback())
|
||||
private fun popDraft() = withState {
|
||||
if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) {
|
||||
// If we were sharing, we want to get back our last value from draft
|
||||
getDraftIfAny()
|
||||
} else {
|
||||
// Otherwise we clear the composer and remove the draft from db
|
||||
setState { copy(sendMode = SendMode.REGULAR("", false)) }
|
||||
room.deleteDraft(NoOpMatrixCallback())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
|
||||
@ -915,74 +927,25 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
|
||||
saveCurrentDraft(action.text)
|
||||
|
||||
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
|
||||
setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) }
|
||||
timelineEvent.root.eventId?.let {
|
||||
room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
|
||||
}
|
||||
setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
|
||||
saveCurrentDraft(action.text)
|
||||
|
||||
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
|
||||
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) }
|
||||
withState { state ->
|
||||
// Save a new draft and keep the previously entered text, if it was not an edit
|
||||
timelineEvent.root.eventId?.let {
|
||||
if (state.sendMode is SendMode.EDIT) {
|
||||
room.saveDraft(UserDraft.QUOTE(it, ""), NoOpMatrixCallback())
|
||||
} else {
|
||||
room.saveDraft(UserDraft.QUOTE(it, action.text), NoOpMatrixCallback())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
|
||||
saveCurrentDraft(action.text)
|
||||
|
||||
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
|
||||
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) }
|
||||
withState { state ->
|
||||
// Save a new draft and keep the previously entered text, if it was not an edit
|
||||
timelineEvent.root.eventId?.let {
|
||||
if (state.sendMode is SendMode.EDIT) {
|
||||
room.saveDraft(UserDraft.REPLY(it, ""), NoOpMatrixCallback())
|
||||
} else {
|
||||
room.saveDraft(UserDraft.REPLY(it, action.text), NoOpMatrixCallback())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveCurrentDraft(draft: String) {
|
||||
// Save the draft with the current text if any
|
||||
withState {
|
||||
if (draft.isNotBlank()) {
|
||||
when (it.sendMode) {
|
||||
is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(draft), NoOpMatrixCallback())
|
||||
is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
|
||||
is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
|
||||
is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
|
||||
if (it.sendMode is SendMode.EDIT) {
|
||||
room.deleteDraft(NoOpMatrixCallback())
|
||||
} else {
|
||||
// Save a new draft and keep the previously entered text
|
||||
room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
|
||||
}
|
||||
setState { copy(sendMode = SendMode.REGULAR(action.text)) }
|
||||
private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState {
|
||||
copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
|
||||
}
|
||||
|
||||
private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
|
||||
|
@ -37,7 +37,12 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
|
||||
*/
|
||||
sealed class SendMode(open val text: String) {
|
||||
data class REGULAR(override val text: String) : SendMode(text)
|
||||
data class REGULAR(
|
||||
override val text: String,
|
||||
val fromSharing: Boolean,
|
||||
// This is necessary for forcing refresh on selectSubscribe
|
||||
private val ts: Long = System.currentTimeMillis()
|
||||
) : SendMode(text)
|
||||
data class QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
|
||||
data class EDIT(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
|
||||
data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
|
||||
@ -58,7 +63,7 @@ data class RoomDetailViewState(
|
||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||
val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
|
||||
val typingMessage: String? = null,
|
||||
val sendMode: SendMode = SendMode.REGULAR(""),
|
||||
val sendMode: SendMode = SendMode.REGULAR("", false),
|
||||
val tombstoneEvent: Event? = null,
|
||||
val tombstoneEventHandling: Async<String> = Uninitialized,
|
||||
val syncState: SyncState = SyncState.Idle,
|
||||
|
@ -25,12 +25,15 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.EpoxyTouchHelperCallback
|
||||
import com.airbnb.epoxy.EpoxyViewHolder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import timber.log.Timber
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context,
|
||||
private var replyButtonProgress: Float = 0F
|
||||
private var lastReplyButtonAnimationTime: Long = 0
|
||||
|
||||
private var imageDrawable: Drawable = ContextCompat.getDrawable(context, actionIcon)!!
|
||||
private val imageDrawable: Drawable = DrawableCompat.wrap(
|
||||
ContextCompat.getDrawable(context, actionIcon)!!
|
||||
)
|
||||
|
||||
init {
|
||||
DrawableCompat.setTint(
|
||||
imageDrawable,
|
||||
ThemeUtils.getColor(context, R.attr.riotx_text_primary)
|
||||
)
|
||||
}
|
||||
|
||||
private val triggerDistance = convertToPx(100)
|
||||
private val minShowDistance = convertToPx(20)
|
||||
|
@ -105,6 +105,7 @@
|
||||
android:layout_marginEnd="4dp"
|
||||
android:src="@drawable/ic_edit"
|
||||
android:visibility="gone"
|
||||
android:tint="?riotx_text_primary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
|
||||
app:layout_constraintStart_toEndOf="@+id/roomNameView"
|
||||
|
Loading…
Reference in New Issue
Block a user