Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send reaction view quick react and picker #138

Merged
merged 2 commits into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ interface SendService {
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable


fun sendReaction(reaction: String, targetEventId: String) : Cancelable

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.room

import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.internal.network.NetworkConstants
Expand Down Expand Up @@ -156,4 +157,20 @@ internal interface RoomAPI {
@Path("state_event_type") stateEventType: String,
@Path("state_key") stateKey: String,
@Body params: Map<String, String>): Call<Unit>

/**
* Send a relation event to a room.
*
* @param txId the transaction Id
* @param roomId the room id
* @param eventType the event type
* @param content the event content
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}")
fun sendRelation(@Path("roomId") roomId: String,
@Path("parentId") parent_id: String,
@Path("relation_type") relationType: String,
@Path("eventType") eventType: String,
@Body content: Content?
): Call<SendResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ internal class DefaultSendService(private val roomId: String,
return cancelableBag
}


override fun sendReaction(reaction: String, targetEventId: String) : Cancelable {
val event = eventFactory.createReactionEvent(roomId,targetEventId,reaction).also {
saveLocalEcho(it)
}
val sendRelationWork = createSendRelationWork(event)
WorkManager.getInstance()
.beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, sendRelationWork)
.enqueue()
return CancelableWork(sendRelationWork.id)
}

override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
// Create an event with the media file path
val event = eventFactory.createMediaEvent(roomId, attachment).also {
Expand Down Expand Up @@ -116,6 +128,19 @@ internal class DefaultSendService(private val roomId: String,
.build()
}

private fun createSendRelationWork(event: Event): OneTimeWorkRequest {
//TODO use the new API to send relation (for now use regular send)
val sendContentWorkerParams = SendEventWorker.Params(
roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)

return OneTimeWorkRequestBuilder<SendEventWorker>()
.setConstraints(WORK_CONSTRAINTS)
.setInputData(sendWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}

private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData): OneTimeWorkRequest {
val uploadMediaWorkerParams = UploadContentWorker.Params(roomId, event, attachment)
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,11 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.message.AudioInfo
import im.vector.matrix.android.api.session.room.model.message.FileInfo
import im.vector.matrix.android.api.session.room.model.message.ImageInfo
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.ThumbnailInfo
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.annotation.ReactionInfo
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor

internal class LocalEchoEventFactory(private val credentials: Credentials) {
Expand All @@ -47,10 +40,29 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
}
}

fun createReactionEvent(roomId: String, targetEventId: String, reaction: String): Event {
val content = ReactionContent(
ReactionInfo(
RelationType.ANNOTATION,
targetEventId,
reaction
)
)
return Event(
roomId = roomId,
originServerTs = dummyOriginServerTs(),
sender = credentials.userId,
eventId = dummyEventId(roomId),
type = EventType.REACTION,
content = content.toContent()
)
}


private fun createImageEvent(roomId: String, attachment: ContentAttachmentData): Event {
val content = MessageImageContent(
type = MessageType.MSGTYPE_IMAGE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package im.vector.matrix.android.internal.session.room.send

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.annotation.ReactionInfo
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject

class SendRelationWorker(context: Context, params: WorkerParameters)
: Worker(context, params), MatrixKoinComponent {


@JsonClass(generateAdapter = true)
internal data class Params(
val roomId: String,
val event: Event,
val relationType: String? = null
)

private val roomAPI by inject<RoomAPI>()

override fun doWork(): Result {
val params = WorkerParamsFactory.fromData<SendRelationWorker.Params>(inputData)
?: return Result.failure()

val localEvent = params.event
if (localEvent.eventId == null) {
return Result.failure()
}
val relationContent = localEvent.content.toModel<ReactionContent>()
?: return Result.failure()
val relatedEventId = relationContent.relatesTo?.eventId ?: return Result.failure()
val relationType = (relationContent.relatesTo as? ReactionInfo)?.type ?: params.relationType
?: return Result.failure()

val result = executeRequest<SendResponse> {
apiCall = roomAPI.sendRelation(
roomId = params.roomId,
parent_id = relatedEventId,
relationType = relationType,
eventType = localEvent.type,
content = localEvent.content
)
}
return result.fold({ Result.retry() }, { Result.success() })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ internal class DefaultTimeline(
val timelineEvent = timelineEventFactory.create(eventEntity)
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
builtEvents.add(position, timelineEvent)
//Need to shift :/
builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) }
builtEventsIdMap[eventEntity.eventId] = position
}
Timber.v("Built ${offsetResults.size} items from db")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ sealed class RoomDetailActions {
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()

data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()



}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ data class RoomDetailArgs(
private const val CAMERA_VALUE_TITLE = "attachment"
private const val REQUEST_FILES_REQUEST_CODE = 0
private const val TAKE_IMAGE_REQUEST_CODE = 1
private const val REACTION_SELECT_REQUEST_CODE = 2

class RoomDetailFragment :
VectorBaseFragment(),
Expand Down Expand Up @@ -182,6 +183,12 @@ class RoomDetailFragment :
if (resultCode == RESULT_OK && data != null) {
when (requestCode) {
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) ?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) ?: return
//TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,eventId))
}
}
}
}
Expand Down Expand Up @@ -476,6 +483,16 @@ class RoomDetailFragment :
override fun onMemberNameClicked(informationData: MessageInformationData) {
insertUserDisplayNameInTextEditor(informationData.memberName?.toString())
}

override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) {
if (on) {
//we should test the current real state of reaction on this event
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,informationData.eventId))
} else {
//TODO it's an undo :/
}
}

// AutocompleteUserPresenter.Callback

override fun onQueryUsers(query: CharSequence?) {
Expand All @@ -487,7 +504,8 @@ class RoomDetailFragment :

when (actionData.actionId) {
MessageMenuViewModel.ACTION_ADD_REACTION -> {
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext()), 0)
val eventId = actionData.data?.toString() ?: return
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
}
MessageMenuViewModel.ACTION_COPY -> {
//I need info about the current selected message :/
Expand Down Expand Up @@ -539,6 +557,11 @@ class RoomDetailFragment :
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
.show()
}
MessageMenuViewModel.ACTION_QUICK_REACT -> {
(actionData.data as? Pair<String, String>)?.let { pairData ->
roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first))
}
}
else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
}
Expand All @@ -553,6 +576,7 @@ class RoomDetailFragment :
* @param text the text to insert.
*/
private fun insertUserDisplayNameInTextEditor(text: String?) {
//TODO move logic outside of fragment
if (null != text) {
// var vibrate = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,

fun process(action: RoomDetailActions) {
when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action)
is RoomDetailActions.SendReaction -> handleSendReaction(action)
}
}

Expand All @@ -88,63 +89,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
val slashCommandResult = CommandParser.parseSplashCommand(action.text)

when (slashCommandResult) {
is ParsedCommand.ErrorNotACommand -> {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
is ParsedCommand.ErrorSyntax -> {
is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
}
is ParsedCommand.ErrorEmptySlashCommand -> {
is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
}
is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
}
is ParsedCommand.Invite -> {
is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult)
}
is ParsedCommand.SetUserPowerLevel -> {
is ParsedCommand.SetUserPowerLevel -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.ClearScalarToken -> {
is ParsedCommand.ClearScalarToken -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SetMarkdown -> {
is ParsedCommand.SetMarkdown -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.UnbanUser -> {
is ParsedCommand.UnbanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.BanUser -> {
is ParsedCommand.BanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.KickUser -> {
is ParsedCommand.KickUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.JoinRoom -> {
is ParsedCommand.JoinRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.PartRoom -> {
is ParsedCommand.PartRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SendEmote -> {
is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
}
is ParsedCommand.ChangeTopic -> {
is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult)
}
is ParsedCommand.ChangeDisplayName -> {
is ParsedCommand.ChangeDisplayName -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
Expand Down Expand Up @@ -179,6 +180,11 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
})
}


private fun handleSendReaction(action: RoomDetailActions.SendReaction) {
room.sendReaction(action.reaction,action.targetEventId)
}

private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
val attachments = action.mediaFiles.map {
ContentAttachmentData(
Expand Down
Loading