parent
b45cc0e63f
commit
0e06908a48
|
@ -18,8 +18,10 @@ package im.vector.matrix.android.api.session.events.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import timber.log.Timber
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
typealias Content = Map<String, @JvmSuppressWildcards Any>
|
typealias Content = Map<String, @JvmSuppressWildcards Any>
|
||||||
|
@ -31,7 +33,12 @@ inline fun <reified T> Content?.toModel(): T? {
|
||||||
return this?.let {
|
return this?.let {
|
||||||
val moshi = MoshiProvider.providesMoshi()
|
val moshi = MoshiProvider.providesMoshi()
|
||||||
val moshiAdapter = moshi.adapter(T::class.java)
|
val moshiAdapter = moshi.adapter(T::class.java)
|
||||||
return moshiAdapter.fromJsonValue(it)
|
try {
|
||||||
|
return moshiAdapter.fromJsonValue(it)
|
||||||
|
} catch (e: JsonDataException) {
|
||||||
|
Timber.e(e, "Failed to parse content")
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.annotation.ReactionService
|
import im.vector.matrix.android.api.session.room.model.annotation.RelationService
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
import im.vector.matrix.android.api.session.room.state.StateService
|
||||||
|
@ -28,7 +28,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to interact within a room.
|
* This interface defines methods to interact within a room.
|
||||||
*/
|
*/
|
||||||
interface Room : TimelineService, SendService, ReadService, MembershipService, StateService , ReactionService{
|
interface Room : TimelineService, SendService, ReadService, MembershipService, StateService , RelationService{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The roomId of this room
|
* The roomId of this room
|
||||||
|
|
|
@ -7,5 +7,7 @@ import com.squareup.moshi.JsonClass
|
||||||
data class ReactionInfo(
|
data class ReactionInfo(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String,
|
@Json(name = "event_id") override val eventId: String,
|
||||||
val key: String
|
val key: String,
|
||||||
|
//always null for reaction
|
||||||
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
|
@ -3,4 +3,5 @@ package im.vector.matrix.android.api.session.room.model.annotation
|
||||||
interface RelationContent {
|
interface RelationContent {
|
||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
|
val inReplyTo: ReplyToContent?
|
||||||
}
|
}
|
|
@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RelationDefaultContent(
|
data class RelationDefaultContent(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String?
|
@Json(name = "event_id") override val eventId: String?,
|
||||||
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
|
|
@ -15,10 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.session.room.model.annotation
|
package im.vector.matrix.android.api.session.room.model.annotation
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
//TODO rename in relationService?
|
//TODO rename in relationService?
|
||||||
interface ReactionService {
|
interface RelationService {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,4 +62,7 @@ interface ReactionService {
|
||||||
*/
|
*/
|
||||||
fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
|
||||||
|
fun replyToMessage(eventReplied: Event, replyText: String) : Cancelable?
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model.annotation
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ReplyToContent(
|
||||||
|
@Json(name = "event_id") val eventId: String
|
||||||
|
)
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
|
|
||||||
|
@ -39,6 +40,9 @@ class MatrixModule(private val context: Context) {
|
||||||
single {
|
single {
|
||||||
TaskExecutor(get())
|
TaskExecutor(get())
|
||||||
}
|
}
|
||||||
|
single {
|
||||||
|
StringProvider(context.resources)
|
||||||
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
BackgroundDetectionObserver()
|
BackgroundDetectionObserver()
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.annotation.ReactionService
|
import im.vector.matrix.android.api.session.room.model.annotation.RelationService
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
import im.vector.matrix.android.api.session.room.state.StateService
|
||||||
|
@ -40,14 +40,14 @@ internal class DefaultRoom(
|
||||||
private val sendService: SendService,
|
private val sendService: SendService,
|
||||||
private val stateService: StateService,
|
private val stateService: StateService,
|
||||||
private val readService: ReadService,
|
private val readService: ReadService,
|
||||||
private val reactionService: ReactionService,
|
private val relationService: RelationService,
|
||||||
private val roomMembersService: MembershipService
|
private val roomMembersService: MembershipService
|
||||||
) : Room,
|
) : Room,
|
||||||
TimelineService by timelineService,
|
TimelineService by timelineService,
|
||||||
SendService by sendService,
|
SendService by sendService,
|
||||||
StateService by stateService,
|
StateService by stateService,
|
||||||
ReadService by readService,
|
ReadService by readService,
|
||||||
ReactionService by reactionService,
|
RelationService by relationService,
|
||||||
MembershipService by roomMembersService {
|
MembershipService by roomMembersService {
|
||||||
|
|
||||||
override val roomSummary: LiveData<RoomSummary> by lazy {
|
override val roomSummary: LiveData<RoomSummary> by lazy {
|
||||||
|
|
|
@ -18,10 +18,10 @@ package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.annotation.DefaultRelationService
|
||||||
import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask
|
import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask
|
||||||
import im.vector.matrix.android.internal.session.room.annotation.DefaultReactionService
|
|
||||||
import im.vector.matrix.android.internal.session.room.annotation.UpdateQuickReactionTask
|
import im.vector.matrix.android.internal.session.room.annotation.UpdateQuickReactionTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||||
|
@ -58,7 +58,7 @@ internal class RoomFactory(private val monarchy: Monarchy,
|
||||||
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||||
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
|
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
|
||||||
val sendService = DefaultSendService(roomId, eventFactory, monarchy)
|
val sendService = DefaultSendService(roomId, eventFactory, monarchy)
|
||||||
val reactionService = DefaultReactionService(roomId, eventFactory, findReactionEventForUndoTask, updateQuickReactionTask, taskExecutor)
|
val reactionService = DefaultRelationService(roomId, eventFactory, findReactionEventForUndoTask, updateQuickReactionTask, monarchy, taskExecutor)
|
||||||
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
||||||
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
||||||
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask)
|
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask)
|
||||||
|
|
|
@ -39,6 +39,7 @@ import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
|
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.*
|
import im.vector.matrix.android.internal.session.room.timeline.*
|
||||||
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ class RoomModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
LocalEchoEventFactory(get())
|
LocalEchoEventFactory(get(), get())
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
|
@ -109,7 +110,7 @@ class RoomModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
DefaultPruneEventTask(get(),get()) as PruneEventTask
|
DefaultPruneEventTask(get(), get()) as PruneEventTask
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,17 @@
|
||||||
package im.vector.matrix.android.internal.session.room.annotation
|
package im.vector.matrix.android.internal.session.room.annotation
|
||||||
|
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
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.ReactionService
|
import im.vector.matrix.android.api.session.room.model.annotation.RelationService
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
import im.vector.matrix.android.internal.session.room.send.RedactEventWorker
|
import im.vector.matrix.android.internal.session.room.send.RedactEventWorker
|
||||||
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
|
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
|
||||||
|
@ -28,6 +34,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.CancelableWork
|
import im.vector.matrix.android.internal.util.CancelableWork
|
||||||
import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
|
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val REACTION_WORK = "REACTION_WORK"
|
private const val REACTION_WORK = "REACTION_WORK"
|
||||||
|
@ -37,12 +44,13 @@ private val WORK_CONSTRAINTS = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
internal class DefaultReactionService(private val roomId: String,
|
internal class DefaultRelationService(private val roomId: String,
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
private val updateQuickReactionTask: UpdateQuickReactionTask,
|
private val updateQuickReactionTask: UpdateQuickReactionTask,
|
||||||
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor)
|
||||||
: ReactionService {
|
: RelationService {
|
||||||
|
|
||||||
|
|
||||||
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
|
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
|
||||||
|
@ -170,4 +178,38 @@ internal class DefaultReactionService(private val roomId: String,
|
||||||
return CancelableWork(workRequest.id)
|
return CancelableWork(workRequest.id)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reply to an event in the timeline
|
||||||
|
* Users may wish to reference another message when forming their own message
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
||||||
|
*/
|
||||||
|
override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? {
|
||||||
|
val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText)?.also {
|
||||||
|
saveLocalEcho(it)
|
||||||
|
} ?: return null
|
||||||
|
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<SendEventWorker>()
|
||||||
|
.setConstraints(WORK_CONSTRAINTS)
|
||||||
|
.setInputData(sendWorkData)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance()
|
||||||
|
.beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, workRequest)
|
||||||
|
.enqueue()
|
||||||
|
return CancelableWork(workRequest.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLocalEcho(event: Event) {
|
||||||
|
monarchy.tryTransactionAsync { realm ->
|
||||||
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
|
||||||
|
?: return@tryTransactionAsync
|
||||||
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId)
|
||||||
|
?: return@tryTransactionAsync
|
||||||
|
|
||||||
|
roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -17,19 +17,20 @@
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
|
import im.vector.matrix.android.R
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
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.*
|
||||||
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.annotation.ReactionContent
|
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.annotation.ReactionInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.annotation.ReplyToContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||||
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
|
|
||||||
internal class LocalEchoEventFactory(private val credentials: Credentials) {
|
internal class LocalEchoEventFactory(private val credentials: Credentials, private val stringProvider: StringProvider) {
|
||||||
|
|
||||||
fun createTextEvent(roomId: String, msgType: String, text: String): Event {
|
fun createTextEvent(roomId: String, msgType: String, text: String): Event {
|
||||||
val content = MessageTextContent(type = msgType, body = text)
|
val content = MessageTextContent(type = msgType, body = text)
|
||||||
|
@ -183,4 +184,80 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
|
||||||
private fun dummyEventId(roomId: String): String {
|
private fun dummyEventId(roomId: String): String {
|
||||||
return roomId + "-" + dummyOriginServerTs()
|
return roomId + "-" + dummyOriginServerTs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createReplyTextEvent(roomId: String, eventReplied: Event, replyText: String): Event? {
|
||||||
|
//Fallbacks and event representation
|
||||||
|
//TODO Add error/warning logs when any of this is null
|
||||||
|
val permalink = PermalinkFactory.createPermalink(eventReplied) ?: return null
|
||||||
|
val userId = eventReplied.sender ?: return null
|
||||||
|
val userLink = PermalinkFactory.createPermalink(userId) ?: return null
|
||||||
|
// <mx-reply>
|
||||||
|
// <blockquote>
|
||||||
|
// <a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
|
||||||
|
// <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
|
||||||
|
// <br />
|
||||||
|
// <!-- This is where the related event's HTML would be. -->
|
||||||
|
// </blockquote>
|
||||||
|
// </mx-reply>
|
||||||
|
// This is where the reply goes.
|
||||||
|
val body = bodyForReply(eventReplied.content.toModel<MessageContent>())
|
||||||
|
val replyFallbackTemplateFormatted = """
|
||||||
|
<mx-reply>
|
||||||
|
<blockquote>
|
||||||
|
<a href="%s">${stringProvider.getString(R.string.in_reply_to)}</a>
|
||||||
|
<a href="%s">%s</a>
|
||||||
|
<br />
|
||||||
|
%s
|
||||||
|
</blockquote>
|
||||||
|
</mx-reply>
|
||||||
|
%s
|
||||||
|
""".trim().format(permalink, userLink, userId, body.second ?: body.first, replyText)
|
||||||
|
//
|
||||||
|
// > <@alice:example.org> This is the original body
|
||||||
|
//
|
||||||
|
// This is where the reply goes
|
||||||
|
val lines = body.first.split("\n")
|
||||||
|
val plainTextBody = StringBuffer("><${userId}>")
|
||||||
|
lines.firstOrNull()?.also { plainTextBody.append(" $it") }
|
||||||
|
lines.forEachIndexed { index, s ->
|
||||||
|
if (index > 0) {
|
||||||
|
plainTextBody.append("\n>$s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plainTextBody.append("\n\n").append(replyText)
|
||||||
|
|
||||||
|
val eventId = eventReplied.eventId ?: return null
|
||||||
|
val content = MessageTextContent(
|
||||||
|
type = MessageType.MSGTYPE_TEXT,
|
||||||
|
format = MessageType.FORMAT_MATRIX_HTML,
|
||||||
|
body = plainTextBody.toString(),
|
||||||
|
formattedBody = replyFallbackTemplateFormatted,
|
||||||
|
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||||
|
)
|
||||||
|
return createEvent(roomId, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bodyForReply(content: MessageContent?): Pair<String, String?> {
|
||||||
|
when (content?.type) {
|
||||||
|
MessageType.MSGTYPE_EMOTE,
|
||||||
|
MessageType.MSGTYPE_TEXT,
|
||||||
|
MessageType.MSGTYPE_NOTICE -> {
|
||||||
|
//If we already have formatted body, return it?
|
||||||
|
var formattedText: String? = null
|
||||||
|
if (content is MessageTextContent) {
|
||||||
|
if (content.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
|
formattedText = content.formattedBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content.body to formattedText
|
||||||
|
}
|
||||||
|
MessageType.MSGTYPE_FILE -> return stringProvider.getString(R.string.sent_a_file) to null
|
||||||
|
MessageType.MSGTYPE_AUDIO -> return stringProvider.getString(R.string.sent_an_audio_file) to null
|
||||||
|
MessageType.MSGTYPE_IMAGE -> return stringProvider.getString(R.string.sent_an_image) to null
|
||||||
|
MessageType.MSGTYPE_VIDEO -> return stringProvider.getString(R.string.sent_a_video) to null
|
||||||
|
else -> return (content?.body ?: "") to null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
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.internal.di.MatrixKoinComponent
|
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
@ -57,6 +58,11 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
|
||||||
localEvent.content
|
localEvent.content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return result.fold({ Result.retry() }, { Result.success() })
|
return result.fold({
|
||||||
|
when (it) {
|
||||||
|
is Failure.NetworkConnection -> Result.retry()
|
||||||
|
else -> Result.failure()
|
||||||
|
}
|
||||||
|
}, { Result.success() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
class StringProvider(private val resources: Resources) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a localized string from the application's package's
|
||||||
|
* default string table.
|
||||||
|
*
|
||||||
|
* @param resId Resource id for the string
|
||||||
|
* @return The string data associated with the resource, stripped of styled
|
||||||
|
* text information.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
fun getString(@StringRes resId: Int): String {
|
||||||
|
return resources.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a localized formatted string from the application's package's
|
||||||
|
* default string table, substituting the format arguments as defined in
|
||||||
|
* [java.util.Formatter] and [java.lang.String.format].
|
||||||
|
*
|
||||||
|
* @param resId Resource id for the format string
|
||||||
|
* @param formatArgs The format arguments that will be used for
|
||||||
|
* substitution.
|
||||||
|
* @return The string data associated with the resource, formatted and
|
||||||
|
* stripped of styled text information.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resources.getString(resId, *formatArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Strings not defined in Riot -->
|
||||||
|
<string name="in_reply_to">In reply to</string>
|
||||||
|
<string name="sent_a_file">sent a file.</string>
|
||||||
|
<string name="sent_an_image">sent an image.</string>
|
||||||
|
<string name="sent_a_video">sent a video.</string>
|
||||||
|
<string name="sent_an_audio_file">sent an audio file.</string>
|
||||||
|
</resources>
|
|
@ -60,7 +60,7 @@ class HomeModule {
|
||||||
val timelineDateFormatter = TimelineDateFormatter(get())
|
val timelineDateFormatter = TimelineDateFormatter(get())
|
||||||
val timelineMediaSizeProvider = TimelineMediaSizeProvider()
|
val timelineMediaSizeProvider = TimelineMediaSizeProvider()
|
||||||
val colorProvider = ColorProvider(fragment.requireContext())
|
val colorProvider = ColorProvider(fragment.requireContext())
|
||||||
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer)
|
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer,get())
|
||||||
|
|
||||||
val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory,
|
val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory,
|
||||||
roomNameItemFactory = RoomNameItemFactory(get()),
|
roomNameItemFactory = RoomNameItemFactory(get()),
|
||||||
|
|
|
@ -38,6 +38,7 @@ sealed class RoomDetailActions {
|
||||||
|
|
||||||
data class EnterEditMode(val eventId: String) : RoomDetailActions()
|
data class EnterEditMode(val eventId: String) : RoomDetailActions()
|
||||||
data class EnterQuoteMode(val eventId: String) : RoomDetailActions()
|
data class EnterQuoteMode(val eventId: String) : RoomDetailActions()
|
||||||
|
data class EnterReplyMode(val eventId: String) : RoomDetailActions()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -208,7 +208,8 @@ class RoomDetailFragment :
|
||||||
composerLayout.collapse()
|
composerLayout.collapse()
|
||||||
}
|
}
|
||||||
SendMode.EDIT,
|
SendMode.EDIT,
|
||||||
SendMode.QUOTE -> {
|
SendMode.QUOTE,
|
||||||
|
SendMode.REPLY -> {
|
||||||
commandAutocompletePolicy.enabled = false
|
commandAutocompletePolicy.enabled = false
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
//we should ignore? can this happen?
|
//we should ignore? can this happen?
|
||||||
|
@ -233,9 +234,12 @@ class RoomDetailFragment :
|
||||||
if (mode == SendMode.EDIT) {
|
if (mode == SendMode.EDIT) {
|
||||||
composerLayout.composerEditText.setText(eventTextBody)
|
composerLayout.composerEditText.setText(eventTextBody)
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
|
||||||
} else {
|
} else if (mode == SendMode.QUOTE) {
|
||||||
composerLayout.composerEditText.setText("")
|
composerLayout.composerEditText.setText("")
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_quote))
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_quote))
|
||||||
|
} else if (mode == SendMode.REPLY) {
|
||||||
|
composerLayout.composerEditText.setText("")
|
||||||
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_reply))
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarRenderer.render(event.senderAvatar, event.root.sender
|
AvatarRenderer.render(event.senderAvatar, event.root.sender
|
||||||
|
@ -673,13 +677,17 @@ class RoomDetailFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_EDIT -> {
|
MessageMenuViewModel.ACTION_EDIT -> {
|
||||||
val eventId = actionData.data.toString() ?: return@let
|
val eventId = actionData.data.toString()
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
|
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_QUOTE -> {
|
MessageMenuViewModel.ACTION_QUOTE -> {
|
||||||
val eventId = actionData.data.toString() ?: return@let
|
val eventId = actionData.data.toString()
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
|
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
|
||||||
}
|
}
|
||||||
|
MessageMenuViewModel.ACTION_REPLY -> {
|
||||||
|
val eventId = actionData.data.toString()
|
||||||
|
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
||||||
|
}
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action)
|
is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action)
|
||||||
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
||||||
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
||||||
|
is RoomDetailActions.EnterReplyMode -> handleReplyAction(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,24 +209,34 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
}
|
}
|
||||||
SendMode.QUOTE -> {
|
SendMode.QUOTE -> {
|
||||||
withState { state ->
|
val messageContent: MessageContent? =
|
||||||
val messageContent: MessageContent? =
|
state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel()
|
?: state.selectedEvent?.root?.content.toModel()
|
||||||
?: state.selectedEvent?.root?.content.toModel()
|
val textMsg = messageContent?.body
|
||||||
val textMsg = messageContent?.body
|
|
||||||
|
|
||||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||||
|
|
||||||
//TODO Refactor this, just temporary for quotes
|
//TODO Refactor this, just temporary for quotes
|
||||||
val parser = Parser.builder().build()
|
val parser = Parser.builder().build()
|
||||||
val document = parser.parse(finalText)
|
val document = parser.parse(finalText)
|
||||||
val renderer = HtmlRenderer.builder().build()
|
val renderer = HtmlRenderer.builder().build()
|
||||||
val htmlText = renderer.render(document)
|
val htmlText = renderer.render(document)
|
||||||
if (TextUtils.equals(finalText, htmlText)) {
|
if (TextUtils.equals(finalText, htmlText)) {
|
||||||
room.sendTextMessage(finalText)
|
room.sendTextMessage(finalText)
|
||||||
} else {
|
} else {
|
||||||
room.sendFormattedTextMessage(finalText, htmlText)
|
room.sendFormattedTextMessage(finalText, htmlText)
|
||||||
}
|
}
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sendMode = SendMode.REGULAR,
|
||||||
|
selectedEvent = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
|
}
|
||||||
|
SendMode.REPLY -> {
|
||||||
|
state.selectedEvent?.let {
|
||||||
|
room.replyToMessage(it.root, action.text)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR,
|
||||||
|
@ -377,6 +388,17 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private fun handleReplyAction(action: RoomDetailActions.EnterReplyMode) {
|
||||||
|
room.getTimeLineEvent(action.eventId)?.let {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sendMode = SendMode.REPLY,
|
||||||
|
selectedEvent = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun observeEventDisplayedActions() {
|
private fun observeEventDisplayedActions() {
|
||||||
|
|
|
@ -36,7 +36,8 @@ import im.vector.matrix.android.api.session.user.model.User
|
||||||
enum class SendMode {
|
enum class SendMode {
|
||||||
REGULAR,
|
REGULAR,
|
||||||
QUOTE,
|
QUOTE,
|
||||||
EDIT
|
EDIT,
|
||||||
|
REPLY
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoomDetailViewState(
|
data class RoomDetailViewState(
|
||||||
|
|
|
@ -21,8 +21,14 @@ import com.airbnb.mvrx.ViewModelContext
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
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.riotredesign.core.platform.VectorViewModel
|
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
|
import ru.noties.markwon.Markwon
|
||||||
|
import ru.noties.markwon.html.HtmlPlugin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -31,7 +37,7 @@ import java.util.*
|
||||||
data class MessageActionState(
|
data class MessageActionState(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val senderName: String,
|
val senderName: String,
|
||||||
val messageBody: String,
|
val messageBody: CharSequence,
|
||||||
val ts: String?,
|
val ts: String?,
|
||||||
val senderAvatarPath: String? = null)
|
val senderAvatarPath: String? = null)
|
||||||
: MvRxState
|
: MvRxState
|
||||||
|
@ -54,10 +60,19 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
|
||||||
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.content.toModel()
|
?: event.root.content.toModel()
|
||||||
val originTs = event.root.originServerTs
|
val originTs = event.root.originServerTs
|
||||||
|
var body: CharSequence = messageContent?.body ?: ""
|
||||||
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
|
val parser = Parser.builder().build()
|
||||||
|
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
|
||||||
|
// val renderer = HtmlRenderer.builder().build()
|
||||||
|
body = Markwon.builder(viewModelContext.activity)
|
||||||
|
.usePlugin(HtmlPlugin.create()).build().render(document)
|
||||||
|
// body = renderer.render(document)
|
||||||
|
}
|
||||||
MessageActionState(
|
MessageActionState(
|
||||||
event.root.sender ?: "",
|
event.root.sender ?: "",
|
||||||
parcel.informationData.memberName.toString(),
|
parcel.informationData.memberName.toString(),
|
||||||
messageContent?.body ?: "",
|
body,
|
||||||
dateFormat.format(Date(originTs ?: 0)),
|
dateFormat.format(Date(originTs ?: 0)),
|
||||||
currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
|
currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
||||||
//Resend and Delete
|
//Resend and Delete
|
||||||
return MessageMenuState(
|
return MessageMenuState(
|
||||||
listOf(
|
listOf(
|
||||||
SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_corner_down_right, event.root.eventId),
|
SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId),
|
||||||
//TODO delete icon
|
//TODO delete icon
|
||||||
SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
|
SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
|
||||||
)
|
)
|
||||||
|
@ -80,6 +80,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canReply(event, messageContent)) {
|
||||||
|
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, event.root.eventId))
|
||||||
|
}
|
||||||
|
|
||||||
if (canEdit(event, currentSession.sessionParams.credentials.userId)) {
|
if (canEdit(event, currentSession.sessionParams.credentials.userId)) {
|
||||||
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId))
|
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId))
|
||||||
}
|
}
|
||||||
|
@ -93,9 +97,6 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
||||||
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId))
|
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canReply(event, messageContent)) {
|
|
||||||
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_corner_down_right))
|
|
||||||
}
|
|
||||||
if (canShare(type)) {
|
if (canShare(type)) {
|
||||||
if (messageContent is MessageImageContent) {
|
if (messageContent is MessageImageContent) {
|
||||||
this.add(
|
this.add(
|
||||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.core.linkify.VectorLinkify
|
import im.vector.riotredesign.core.linkify.VectorLinkify
|
||||||
import im.vector.riotredesign.core.resources.ColorProvider
|
import im.vector.riotredesign.core.resources.ColorProvider
|
||||||
|
import im.vector.riotredesign.core.resources.StringProvider
|
||||||
import im.vector.riotredesign.core.utils.DebouncedClickListener
|
import im.vector.riotredesign.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
@ -52,7 +53,8 @@ import me.gujun.android.span.span
|
||||||
class MessageItemFactory(private val colorProvider: ColorProvider,
|
class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val timelineDateFormatter: TimelineDateFormatter,
|
private val timelineDateFormatter: TimelineDateFormatter,
|
||||||
private val htmlRenderer: EventHtmlRenderer) {
|
private val htmlRenderer: EventHtmlRenderer,
|
||||||
|
private val stringProvider: StringProvider) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
|
@ -100,8 +102,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
val messageContent: MessageContent =
|
val messageContent: MessageContent =
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.content.toModel()
|
?: event.root.content.toModel()
|
||||||
?: return null
|
?: //Malformed content, we should echo something on screen
|
||||||
|
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||||
|
|
||||||
|
//TODO this should be filtered as not displayable?
|
||||||
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
|
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
|
||||||
//TODO blank item or ignore??
|
//TODO blank item or ignore??
|
||||||
// ignore this event
|
// ignore this event
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="22dp"
|
android:width="22dp"
|
||||||
android:height="22dp"
|
android:height="13dp"
|
||||||
android:viewportWidth="22"
|
android:viewportWidth="22"
|
||||||
android:viewportHeight="22">
|
android:viewportHeight="13">
|
||||||
<path
|
<path
|
||||||
android:pathData="M14.75,8.5L21,14.75 14.75,21"
|
android:pathData="M5.4444,1l-4.4444,4.3636l4.4444,4.3636"
|
||||||
android:strokeLineJoin="round"
|
android:strokeLineJoin="round"
|
||||||
android:strokeWidth="2"
|
android:strokeWidth="2"
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#00000000"
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:strokeColor="#9E9E9E"
|
android:strokeColor="#9E9E9E"
|
||||||
|
android:fillType="evenOdd"
|
||||||
android:strokeLineCap="round"/>
|
android:strokeLineCap="round"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M1,1v8.75a5,5 0,0 0,5 5h15"
|
android:pathData="M21,11.9091L21,9.7273C21,7.3173 19.0102,5.3636 16.5556,5.3636L1,5.3636"
|
||||||
android:strokeLineJoin="round"
|
android:strokeLineJoin="round"
|
||||||
android:strokeWidth="2"
|
android:strokeWidth="2"
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#00000000"
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:strokeColor="#9E9E9E"
|
android:strokeColor="#9E9E9E"
|
||||||
|
android:fillType="evenOdd"
|
||||||
android:strokeLineCap="round"/>
|
android:strokeLineCap="round"/>
|
||||||
</vector>
|
</vector>
|
|
@ -17,4 +17,7 @@
|
||||||
<string name="event_redacted_by_admin_reason">Event moderated by room admin</string>
|
<string name="event_redacted_by_admin_reason">Event moderated by room admin</string>
|
||||||
<string name="last_edited_info_message">Last edited by %s on %s</string>
|
<string name="last_edited_info_message">Last edited by %s on %s</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="malformed_message">Malformed event, cannot display</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue