forked from GitHub-Mirror/riotX-android
Send reaction view quick react and picker
+ fix / Error when to many reactions in cells (more than placeholders -8-) + fix / DefaultTimeline quick map access was not shifted when items inserted at given index
This commit is contained in:
parent
e27367e3f2
commit
ef26519993
@ -49,4 +49,6 @@ interface SendService {
|
|||||||
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
||||||
|
|
||||||
|
|
||||||
|
fun sendReaction(reaction: String, targetEventId: String) : Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -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.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
@ -156,4 +157,20 @@ internal interface RoomAPI {
|
|||||||
@Path("state_event_type") stateEventType: String,
|
@Path("state_event_type") stateEventType: String,
|
||||||
@Path("state_key") stateKey: String,
|
@Path("state_key") stateKey: String,
|
||||||
@Body params: Map<String, String>): Call<Unit>
|
@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>
|
||||||
}
|
}
|
@ -74,6 +74,18 @@ internal class DefaultSendService(private val roomId: String,
|
|||||||
return cancelableBag
|
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 {
|
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
|
||||||
// Create an event with the media file path
|
// Create an event with the media file path
|
||||||
val event = eventFactory.createMediaEvent(roomId, attachment).also {
|
val event = eventFactory.createMediaEvent(roomId, attachment).also {
|
||||||
@ -116,6 +128,19 @@ internal class DefaultSendService(private val roomId: String,
|
|||||||
.build()
|
.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 {
|
private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData): OneTimeWorkRequest {
|
||||||
val uploadMediaWorkerParams = UploadContentWorker.Params(roomId, event, attachment)
|
val uploadMediaWorkerParams = UploadContentWorker.Params(roomId, event, attachment)
|
||||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||||
|
@ -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.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.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.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.AudioInfo
|
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.FileInfo
|
import im.vector.matrix.android.api.session.room.model.annotation.ReactionInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.message.ImageInfo
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
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.internal.session.content.ThumbnailExtractor
|
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||||
|
|
||||||
internal class LocalEchoEventFactory(private val credentials: Credentials) {
|
internal class LocalEchoEventFactory(private val credentials: Credentials) {
|
||||||
@ -47,10 +40,29 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
|
|||||||
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
|
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
|
||||||
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
|
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
|
||||||
ContentAttachmentData.Type.AUDIO -> createAudioEvent(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 {
|
private fun createImageEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||||
val content = MessageImageContent(
|
val content = MessageImageContent(
|
||||||
type = MessageType.MSGTYPE_IMAGE,
|
type = MessageType.MSGTYPE_IMAGE,
|
||||||
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
@ -419,6 +419,8 @@ internal class DefaultTimeline(
|
|||||||
val timelineEvent = timelineEventFactory.create(eventEntity)
|
val timelineEvent = timelineEventFactory.create(eventEntity)
|
||||||
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
||||||
builtEvents.add(position, timelineEvent)
|
builtEvents.add(position, timelineEvent)
|
||||||
|
//Need to shift :/
|
||||||
|
builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) }
|
||||||
builtEventsIdMap[eventEntity.eventId] = position
|
builtEventsIdMap[eventEntity.eventId] = position
|
||||||
}
|
}
|
||||||
Timber.v("Built ${offsetResults.size} items from db")
|
Timber.v("Built ${offsetResults.size} items from db")
|
||||||
|
@ -28,4 +28,8 @@ sealed class RoomDetailActions {
|
|||||||
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
||||||
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
|
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
|
||||||
|
|
||||||
|
data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -107,6 +107,7 @@ data class RoomDetailArgs(
|
|||||||
private const val CAMERA_VALUE_TITLE = "attachment"
|
private const val CAMERA_VALUE_TITLE = "attachment"
|
||||||
private const val REQUEST_FILES_REQUEST_CODE = 0
|
private const val REQUEST_FILES_REQUEST_CODE = 0
|
||||||
private const val TAKE_IMAGE_REQUEST_CODE = 1
|
private const val TAKE_IMAGE_REQUEST_CODE = 1
|
||||||
|
private const val REACTION_SELECT_REQUEST_CODE = 2
|
||||||
|
|
||||||
class RoomDetailFragment :
|
class RoomDetailFragment :
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
@ -182,6 +183,12 @@ class RoomDetailFragment :
|
|||||||
if (resultCode == RESULT_OK && data != null) {
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -487,7 +494,8 @@ class RoomDetailFragment :
|
|||||||
|
|
||||||
when (actionData.actionId) {
|
when (actionData.actionId) {
|
||||||
MessageMenuViewModel.ACTION_ADD_REACTION -> {
|
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 -> {
|
MessageMenuViewModel.ACTION_COPY -> {
|
||||||
//I need info about the current selected message :/
|
//I need info about the current selected message :/
|
||||||
@ -539,6 +547,11 @@ class RoomDetailFragment :
|
|||||||
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
MessageMenuViewModel.ACTION_QUICK_REACT -> {
|
||||||
|
(actionData.data as? Pair<String, String>)?.let { pairData ->
|
||||||
|
roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first))
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
|
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
@ -69,11 +69,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
|
|
||||||
fun process(action: RoomDetailActions) {
|
fun process(action: RoomDetailActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||||
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||||
is RoomDetailActions.SendMedia -> handleSendMedia(action)
|
is RoomDetailActions.SendMedia -> handleSendMedia(action)
|
||||||
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||||
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
||||||
|
is RoomDetailActions.SendReaction -> handleSendReaction(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,63 +89,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
val slashCommandResult = CommandParser.parseSplashCommand(action.text)
|
val slashCommandResult = CommandParser.parseSplashCommand(action.text)
|
||||||
|
|
||||||
when (slashCommandResult) {
|
when (slashCommandResult) {
|
||||||
is ParsedCommand.ErrorNotACommand -> {
|
is ParsedCommand.ErrorNotACommand -> {
|
||||||
// Send the text message to the room
|
// Send the text message to the room
|
||||||
room.sendTextMessage(action.text)
|
room.sendTextMessage(action.text)
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorSyntax -> {
|
is ParsedCommand.ErrorSyntax -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorEmptySlashCommand -> {
|
is ParsedCommand.ErrorEmptySlashCommand -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite -> {
|
is ParsedCommand.Invite -> {
|
||||||
handleInviteSlashCommand(slashCommandResult)
|
handleInviteSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetUserPowerLevel -> {
|
is ParsedCommand.SetUserPowerLevel -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ClearScalarToken -> {
|
is ParsedCommand.ClearScalarToken -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetMarkdown -> {
|
is ParsedCommand.SetMarkdown -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnbanUser -> {
|
is ParsedCommand.UnbanUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.BanUser -> {
|
is ParsedCommand.BanUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.KickUser -> {
|
is ParsedCommand.KickUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinRoom -> {
|
is ParsedCommand.JoinRoom -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.PartRoom -> {
|
is ParsedCommand.PartRoom -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendEmote -> {
|
is ParsedCommand.SendEmote -> {
|
||||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeTopic -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(slashCommandResult)
|
handleChangeTopicSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayName -> {
|
is ParsedCommand.ChangeDisplayName -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
||||||
}
|
}
|
||||||
@ -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) {
|
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
|
||||||
val attachments = action.mediaFiles.map {
|
val attachments = action.mediaFiles.map {
|
||||||
ContentAttachmentData(
|
ContentAttachmentData(
|
||||||
|
@ -95,8 +95,13 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
|
|||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
|
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
|
||||||
override fun didQuickReactWith(reactions: List<String>) {
|
override fun didQuickReactWith(clikedOn: String, reactions: List<String>, eventId: String) {
|
||||||
actionHandlerModel.fireAction("Quick React", reactions)
|
if (reactions.contains(clikedOn)) {
|
||||||
|
//it's an add
|
||||||
|
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Pair(eventId,clikedOn))
|
||||||
|
} else {
|
||||||
|
//it's a remove
|
||||||
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
|
|
||||||
//TODO determine if can copy, forward, reply, quote, report?
|
//TODO determine if can copy, forward, reply, quote, report?
|
||||||
val actions = ArrayList<SimpleAction>().apply {
|
val actions = ArrayList<SimpleAction>().apply {
|
||||||
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_smile))
|
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_smile, event.root.eventId))
|
||||||
if (canCopy(type)) {
|
if (canCopy(type)) {
|
||||||
//TODO copy images? html? see ClipBoard
|
//TODO copy images? html? see ClipBoard
|
||||||
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body))
|
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body))
|
||||||
@ -184,6 +184,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
|
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
|
||||||
const val PERMALINK = "PERMALINK"
|
const val PERMALINK = "PERMALINK"
|
||||||
const val ACTION_FLAG = "ACTION_FLAG"
|
const val ACTION_FLAG = "ACTION_FLAG"
|
||||||
|
const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -119,12 +119,12 @@ class QuickReactionFragment : BaseMvRxFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (it.selectionResult != null) {
|
if (it.selectionResult != null) {
|
||||||
interactionListener?.didQuickReactWith(it.selectionResult)
|
interactionListener?.didQuickReactWith(it.selectionResult.first, it.selectionResult.second, it.eventId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InteractionListener {
|
interface InteractionListener {
|
||||||
fun didQuickReactWith(reactions: List<String>)
|
fun didQuickReactWith(clikedOn: String, reactions: List<String>, eventId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -31,7 +31,12 @@ enum class TriggleState {
|
|||||||
SECOND
|
SECOND
|
||||||
}
|
}
|
||||||
|
|
||||||
data class QuickReactionState(val agreeTrigleState: TriggleState, val likeTriggleState: TriggleState, val selectionResult: List<String>? = null) : MvRxState
|
data class QuickReactionState(
|
||||||
|
val agreeTrigleState: TriggleState,
|
||||||
|
val likeTriggleState: TriggleState,
|
||||||
|
/** Pair of 'clickedOn' and current toggles state*/
|
||||||
|
val selectionResult: Pair<String, List<String>>? = null,
|
||||||
|
val eventId: String) : MvRxState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick reaction view model
|
* Quick reaction view model
|
||||||
@ -43,16 +48,18 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
|
|||||||
fun toggleAgree(isFirst: Boolean) = withState {
|
fun toggleAgree(isFirst: Boolean) = withState {
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
setState {
|
setState {
|
||||||
|
val newTriggle = if (it.agreeTrigleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
|
||||||
copy(
|
copy(
|
||||||
agreeTrigleState = if (it.agreeTrigleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST,
|
agreeTrigleState = newTriggle,
|
||||||
selectionResult = getReactions(this)
|
selectionResult = Pair(agreePositive, getReactions(this, newTriggle, null))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState {
|
setState {
|
||||||
|
val newTriggle = if (it.agreeTrigleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
|
||||||
copy(
|
copy(
|
||||||
agreeTrigleState = if (it.agreeTrigleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND,
|
agreeTrigleState = agreeTrigleState,
|
||||||
selectionResult = getReactions(this)
|
selectionResult = Pair(agreeNegative, getReactions(this, newTriggle, null))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,30 +68,32 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
|
|||||||
fun toggleLike(isFirst: Boolean) = withState {
|
fun toggleLike(isFirst: Boolean) = withState {
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
setState {
|
setState {
|
||||||
|
val newTriggle = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
|
||||||
copy(
|
copy(
|
||||||
likeTriggleState = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST,
|
likeTriggleState = newTriggle,
|
||||||
selectionResult = getReactions(this)
|
selectionResult = Pair(likePositive, getReactions(this, null, newTriggle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState {
|
setState {
|
||||||
|
val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
|
||||||
copy(
|
copy(
|
||||||
likeTriggleState = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND,
|
likeTriggleState = newTriggle,
|
||||||
selectionResult = getReactions(this)
|
selectionResult = Pair(likeNegative, getReactions(this, null, newTriggle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getReactions(state: QuickReactionState): List<String> {
|
private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> {
|
||||||
return ArrayList<String>(4).apply {
|
return ArrayList<String>(4).apply {
|
||||||
when (state.likeTriggleState) {
|
when (newState2 ?: state.likeTriggleState) {
|
||||||
TriggleState.FIRST -> add(likePositive)
|
TriggleState.FIRST -> add(likePositive)
|
||||||
TriggleState.SECOND -> add(likeNegative)
|
TriggleState.SECOND -> add(likeNegative)
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (state.agreeTrigleState) {
|
when (newState1 ?: state.agreeTrigleState) {
|
||||||
TriggleState.FIRST -> add(agreePositive)
|
TriggleState.FIRST -> add(agreePositive)
|
||||||
TriggleState.SECOND -> add(agreeNegative)
|
TriggleState.SECOND -> add(agreeNegative)
|
||||||
else -> {
|
else -> {
|
||||||
@ -126,7 +135,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QuickReactionState(agreeTriggle, likeTriggle)
|
return QuickReactionState(agreeTriggle, likeTriggle, null, event.root.eventId ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -88,8 +88,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
//clear all reaction buttons (but not the Flow helper!)
|
//clear all reaction buttons (but not the Flow helper!)
|
||||||
holder.reactionWrapper?.children?.forEach { (it as? ReactionButton)?.isGone = true }
|
holder.reactionWrapper?.children?.forEach { (it as? ReactionButton)?.isGone = true }
|
||||||
val idToRefInFlow = ArrayList<Int>()
|
val idToRefInFlow = ArrayList<Int>()
|
||||||
informationData.orderedReactionList?.forEachIndexed { index, reaction ->
|
informationData.orderedReactionList?.chunked(7)?.firstOrNull()?.forEachIndexed { index, reaction ->
|
||||||
(holder.reactionWrapper?.children?.elementAt(index) as? ReactionButton)?.let { reactionButton ->
|
(holder.reactionWrapper?.children?.elementAtOrNull(index) as? ReactionButton)?.let { reactionButton ->
|
||||||
reactionButton.isVisible = true
|
reactionButton.isVisible = true
|
||||||
idToRefInFlow.add(reactionButton.id)
|
idToRefInFlow.add(reactionButton.id)
|
||||||
reactionButton.reactionString = reaction.first
|
reactionButton.reactionString = reaction.first
|
||||||
|
@ -18,22 +18,34 @@ package im.vector.riotredesign.features.reactions
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import im.vector.riotredesign.core.utils.LiveEvent
|
||||||
|
|
||||||
class EmojiChooserViewModel : ViewModel() {
|
class EmojiChooserViewModel : ViewModel() {
|
||||||
|
|
||||||
var adapter: EmojiRecyclerAdapter? = null
|
var adapter: EmojiRecyclerAdapter? = null
|
||||||
val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
|
val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
|
||||||
|
|
||||||
|
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||||
|
var selectedReaction: String? = null
|
||||||
|
var eventId: String? = null
|
||||||
|
|
||||||
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
||||||
|
|
||||||
|
var reactionClickListener = object : ReactionClickListener {
|
||||||
|
override fun onReactionSelected(reaction: String) {
|
||||||
|
selectedReaction = reaction
|
||||||
|
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun initWithContect(context: Context) {
|
fun initWithContect(context: Context) {
|
||||||
//TODO load async
|
//TODO load async
|
||||||
val emojiDataSource = EmojiDataSource(context)
|
val emojiDataSource = EmojiDataSource(context)
|
||||||
emojiSourceLiveData.value = emojiDataSource
|
emojiSourceLiveData.value = emojiDataSource
|
||||||
adapter = EmojiRecyclerAdapter(emojiDataSource)
|
adapter = EmojiRecyclerAdapter(emojiDataSource, reactionClickListener)
|
||||||
adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
|
adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
|
||||||
override fun firstVisibleSectionChange(section: Int) {
|
override fun firstVisibleSectionChange(section: Int) {
|
||||||
currentSection.value = section
|
currentSection.value = section
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -42,4 +54,8 @@ class EmojiChooserViewModel : ViewModel() {
|
|||||||
fun scrollToSection(sectionIndex: Int) {
|
fun scrollToSection(sectionIndex: Int) {
|
||||||
adapter?.scrollToSection(sectionIndex)
|
adapter?.scrollToSection(sectionIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NAVIGATE_FINISH = "NAVIGATE_FINISH"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.riotredesign.features.reactions
|
package im.vector.riotredesign.features.reactions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
@ -88,6 +89,8 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(EmojiChooserViewModel::class.java)
|
viewModel = ViewModelProviders.of(this).get(EmojiChooserViewModel::class.java)
|
||||||
|
|
||||||
|
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
|
||||||
|
|
||||||
viewModel.emojiSourceLiveData.observe(this, Observer {
|
viewModel.emojiSourceLiveData.observe(this, Observer {
|
||||||
it.rawData?.categories?.let { categories ->
|
it.rawData?.categories?.let { categories ->
|
||||||
for (category in categories) {
|
for (category in categories) {
|
||||||
@ -106,6 +109,19 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
|
|||||||
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
viewModel.navigateEvent.observe(this, Observer {
|
||||||
|
it.getContentIfNotHandled()?.let {
|
||||||
|
if (it == EmojiChooserViewModel.NAVIGATE_FINISH) {
|
||||||
|
//finish with result
|
||||||
|
val dataResult = Intent()
|
||||||
|
dataResult.putExtra(EXTRA_REACTION_RESULT, viewModel.selectedReaction)
|
||||||
|
dataResult.putExtra(EXTRA_EVENT_ID, viewModel.eventId)
|
||||||
|
setResult(Activity.RESULT_OK, dataResult)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestEmojivUnicode10CompatibleFont() {
|
private fun requestEmojivUnicode10CompatibleFont() {
|
||||||
@ -172,9 +188,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun intent(context: Context): Intent {
|
|
||||||
|
const val EXTRA_EVENT_ID = "EXTRA_EVENT_ID"
|
||||||
|
const val EXTRA_REACTION_RESULT = "EXTRA_REACTION_RESULT"
|
||||||
|
|
||||||
|
fun intent(context: Context, eventId: String): Intent {
|
||||||
val intent = Intent(context, EmojiReactionPickerActivity::class.java)
|
val intent = Intent(context, EmojiReactionPickerActivity::class.java)
|
||||||
// intent.putExtra(EXTRA_MATRIX_ID, matrixID)
|
intent.putExtra(EXTRA_EVENT_ID, eventId)
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ import kotlin.math.abs
|
|||||||
* TODO: Performances
|
* TODO: Performances
|
||||||
* TODO: Scroll to section - Find a way to snap section to the top
|
* TODO: Scroll to section - Find a way to snap section to the top
|
||||||
*/
|
*/
|
||||||
class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null) :
|
class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null, var reactionClickListener: ReactionClickListener?) :
|
||||||
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var interactionListener: InteractionListener? = null
|
var interactionListener: InteractionListener? = null
|
||||||
@ -64,6 +64,22 @@ class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null) :
|
|||||||
|
|
||||||
val toUpdateWhenNotBusy = ArrayList<Pair<String, EmojiViewHolder>>()
|
val toUpdateWhenNotBusy = ArrayList<Pair<String, EmojiViewHolder>>()
|
||||||
|
|
||||||
|
val itemClickListener = View.OnClickListener { view ->
|
||||||
|
mRecyclerView?.getChildLayoutPosition(view)?.let { itemPosition ->
|
||||||
|
if (itemPosition != RecyclerView.NO_POSITION) {
|
||||||
|
val categories = dataSource?.rawData?.categories ?: return@OnClickListener
|
||||||
|
val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
|
||||||
|
if (!isSection(itemPosition)) {
|
||||||
|
val sectionMojis = categories[sectionNumber].emojis
|
||||||
|
val sectionOffset = getSectionOffset(sectionNumber)
|
||||||
|
val emoji = sectionMojis[itemPosition - sectionOffset]
|
||||||
|
val item = dataSource.rawData!!.emojis.getValue(emoji).emojiString()
|
||||||
|
reactionClickListener?.onReactionSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
super.onAttachedToRecyclerView(recyclerView)
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
@ -113,6 +129,7 @@ class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null) :
|
|||||||
beginTraceSession("MyAdapter.onCreateViewHolder")
|
beginTraceSession("MyAdapter.onCreateViewHolder")
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
val itemView = inflater.inflate(viewType, parent, false)
|
val itemView = inflater.inflate(viewType, parent, false)
|
||||||
|
itemView.setOnClickListener(itemClickListener)
|
||||||
val viewHolder = when (viewType) {
|
val viewHolder = when (viewType) {
|
||||||
R.layout.grid_section_header -> SectionViewHolder(itemView)
|
R.layout.grid_section_header -> SectionViewHolder(itemView)
|
||||||
else -> EmojiViewHolder(itemView)
|
else -> EmojiViewHolder(itemView)
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package im.vector.riotredesign.features.reactions
|
||||||
|
|
||||||
|
interface ReactionClickListener {
|
||||||
|
fun onReactionSelected(reaction: String)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user