Merge pull request #2158 from vector-im/feature/fix_draft

Feature/fix draft
This commit is contained in:
Benoit Marty 2020-10-06 12:17:50 +02:00 committed by GitHub
commit cac3475281
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 188 additions and 235 deletions

View file

@ -16,6 +16,7 @@ Improvements 🙌:
Bugfix 🐛: Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376) - Improve support for image/audio/video/file selection with intent changes (#1376)
- Fix Splash layout on small screens - Fix Splash layout on small screens
- Simplifies draft management and should fix bunch of draft issues (#952, #683)
Translations 🗣: Translations 🗣:
- -

View file

@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable() return room.getEventReadReceiptsLive(eventId).asObservable()
} }
fun liveDrafts(): Observable<List<UserDraft>> { fun liveDraft(): Observable<Optional<UserDraft>> {
return room.getDraftsLive().asObservable() return room.getDraftLive().asObservable()
.startWithCallable {
room.getDraft().toOptional()
}
} }
fun liveNotificationState(): Observable<RoomNotificationState> { fun liveNotificationState(): Observable<RoomNotificationState> {

View file

@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
interface DraftService { interface DraftService {
@ -34,8 +35,12 @@ interface DraftService {
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
/** /**
* Return the current drafts if any, as a live data * Return the current draft or null
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/ */
fun getDraftsLive(): LiveData<List<UserDraft>> fun getDraft(): UserDraft?
/**
* Return the current draft if any, as a live data
*/
fun getDraftLive(): LiveData<Optional<UserDraft>>
} }

View file

@ -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.DraftService
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers 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) return draftRepository.getDraftsLive(roomId)
} }
} }

View file

@ -20,43 +20,67 @@ package org.matrix.android.sdk.internal.session.room.draft
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy 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.BuildConfig
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.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.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.UserDraftsEntity import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
monarchy.awaitTransaction { monarchy.awaitTransaction {
saveDraft(it, userDraft, roomId) saveDraftInDb(it, userDraft, roomId)
} }
} }
suspend fun deleteDraft(roomId: String) { suspend fun deleteDraft(roomId: String) {
monarchy.awaitTransaction { monarchy.awaitTransaction {
deleteDraft(it, roomId) deleteDraftFromDb(it, roomId)
} }
} }
private fun deleteDraft(realm: Realm, roomId: String) { fun getDraft(roomId: String): UserDraft? {
UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity -> return realmSessionProvider.withRealm { realm ->
if (userDraftsEntity.userDrafts.isNotEmpty()) { UserDraftsEntity.where(realm, roomId).findFirst()
userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1) ?.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() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
@ -68,62 +92,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
userDraftsEntity.let { userDraftEntity -> userDraftsEntity.let { userDraftEntity ->
// Save only valid draft // Save only valid draft
if (draft.isValid()) { if (draft.isValid()) {
// Add a new draft or update the current one? // Replace the current draft
val newDraft = DraftMapper.map(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)}") Timber.d("Draft: create a new draft ${privacySafe(draft)}")
userDraftEntity.userDrafts.clear()
userDraftEntity.userDrafts.add(newDraft) 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
}
}
} else { } else {
// There is no draft to save, so the composer was clear // There is no draft to save, so the composer was clear
Timber.d("Draft: delete a draft") Timber.d("Draft: delete a draft")
val topDraft = userDraftEntity.userDrafts.lastOrNull() val topDraft = userDraftEntity.userDrafts.lastOrNull()
if (topDraft == null) { if (topDraft == null) {
Timber.d("Draft: nothing to do") Timber.d("Draft: nothing to do")
} else { } 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 { private fun privacySafe(o: Any): Any {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
return o return o

View file

@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterQuoteMode(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 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 ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction() data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()

View file

@ -485,8 +485,7 @@ class RoomDetailFragment @Inject constructor(
if (savedInstanceState == null) { if (savedInstanceState == null) {
when (val sharedData = roomDetailArgs.sharedData) { when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> { is SharedData.Text -> {
// Save a draft to set the shared text to the composer roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text))
} }
is SharedData.Attachments -> { is SharedData.Attachments -> {
// open share edition // open share edition
@ -1014,7 +1013,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onCloseRelatedMessage() { override fun onCloseRelatedMessage() {
roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false))
} }
override fun onRichContentSelected(contentUri: Uri): Boolean { override fun onRichContentSelected(contentUri: Uri): Boolean {
@ -1147,12 +1146,8 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is RoomDetailViewEvents.MessageSent -> {
updateComposerText("")
}
is RoomDetailViewEvents.SlashCommandHandled -> { is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
updateComposerText("")
} }
is RoomDetailViewEvents.SlashCommandError -> { is RoomDetailViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))

View file

@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor(
getUnreadState() getUnreadState()
observeSyncState() observeSyncState()
observeEventDisplayedActions() observeEventDisplayedActions()
observeDrafts() getDraftIfAny()
observeUnreadState() observeUnreadState()
observeMyRoomMember() observeMyRoomMember()
observeActiveRoomWidgets() observeActiveRoomWidgets()
@ -240,7 +240,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RedactAction -> handleRedactEvent(action) is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action) is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action) is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action) is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
@ -449,48 +449,53 @@ class RoomDetailViewModel @AssistedInject constructor(
/** /**
* Convert a send mode to a draft and save the draft * Convert a send mode to a draft and save the draft
*/ */
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) { private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
withState { when {
when (it.sendMode) { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback()) setState { copy(sendMode = it.sendMode.copy(action.draft)) }
is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) room.saveDraft(UserDraft.REGULAR(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()) it.sendMode is SendMode.REPLY -> {
}.exhaustive 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() { private fun getDraftIfAny() {
room.rx().liveDrafts() val currentDraft = room.getDraft() ?: return
.subscribe {
Timber.d("Draft update --> SetState")
setState { setState {
val draft = it.lastOrNull() ?: UserDraft.REGULAR("")
copy( copy(
// Create a sendMode from a draft and retrieve the TimelineEvent // Create a sendMode from a draft and retrieve the TimelineEvent
sendMode = when (draft) { sendMode = when (currentDraft) {
is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
is UserDraft.QUOTE -> { is UserDraft.QUOTE -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.QUOTE(timelineEvent, draft.text) SendMode.QUOTE(timelineEvent, currentDraft.text)
} }
} }
is UserDraft.REPLY -> { is UserDraft.REPLY -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.REPLY(timelineEvent, draft.text) SendMode.REPLY(timelineEvent, currentDraft.text)
} }
} }
is UserDraft.EDIT -> { is UserDraft.EDIT -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.EDIT(timelineEvent, draft.text) SendMode.EDIT(timelineEvent, currentDraft.text)
} }
} }
} ?: SendMode.REGULAR("") } ?: SendMode.REGULAR("", fromSharing = false)
) )
} }
} }
.disposeOnClear()
}
private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) { private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) {
if (vectorPreferences.sendTypingNotifs()) { if (vectorPreferences.sendTypingNotifs()) {
@ -740,9 +745,16 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} }
private fun popDraft() { 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()) room.deleteDraft(NoOpMatrixCallback())
} }
}
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> { session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> {
@ -915,74 +927,25 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
timelineEvent.root.eventId?.let {
room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
}
} }
} }
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) { private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } 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) { private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } 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 { private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState {
if (it.sendMode is SendMode.EDIT) { copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
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 handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {

View file

@ -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...) * Depending on the state the bottom toolbar will change (icons/preview/actions...)
*/ */
sealed class SendMode(open val text: String) { 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 QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
data class EDIT(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) 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 asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val activeRoomWidgets: Async<List<Widget>> = Uninitialized, val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
val typingMessage: String? = null, val typingMessage: String? = null,
val sendMode: SendMode = SendMode.REGULAR(""), val sendMode: SendMode = SendMode.REGULAR("", false),
val tombstoneEvent: Event? = null, val tombstoneEvent: Event? = null,
val tombstoneEventHandling: Async<String> = Uninitialized, val tombstoneEventHandling: Async<String> = Uninitialized,
val syncState: SyncState = SyncState.Idle, val syncState: SyncState = SyncState.Idle,

View file

@ -25,12 +25,15 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyTouchHelperCallback
import com.airbnb.epoxy.EpoxyViewHolder import com.airbnb.epoxy.EpoxyViewHolder
import im.vector.app.R
import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber import timber.log.Timber
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context,
private var replyButtonProgress: Float = 0F private var replyButtonProgress: Float = 0F
private var lastReplyButtonAnimationTime: Long = 0 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 triggerDistance = convertToPx(100)
private val minShowDistance = convertToPx(20) private val minShowDistance = convertToPx(20)

View file

@ -105,6 +105,7 @@
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:src="@drawable/ic_edit" android:src="@drawable/ic_edit"
android:visibility="gone" android:visibility="gone"
android:tint="?riotx_text_primary"
app:layout_constraintBottom_toBottomOf="@+id/roomNameView" app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView" app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
app:layout_constraintStart_toEndOf="@+id/roomNameView" app:layout_constraintStart_toEndOf="@+id/roomNameView"