forked from GitHub-Mirror/riotX-android
Merge pull request #459 from vector-im/feature/clenup_after_hol
Review of merged PRs
This commit is contained in:
commit
d9f448c9aa
@ -149,7 +149,7 @@ object MatrixPatterns {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val index = matrixId.lastIndexOf(":")
|
val index = matrixId.indexOf(":")
|
||||||
|
|
||||||
return if (index == -1) {
|
return if (index == -1) {
|
||||||
null
|
null
|
||||||
|
@ -82,8 +82,13 @@ data class Event(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
@Transient
|
||||||
var mxDecryptionResult: OlmDecryptionResult? = null
|
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
var sendState: SendState = SendState.UNKNOWN
|
var sendState: SendState = SendState.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
@ -99,42 +104,6 @@ data class Event(
|
|||||||
// Crypto
|
// Crypto
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
|
|
||||||
// /**
|
|
||||||
// * For encrypted events, the plaintext payload for the event.
|
|
||||||
// * This is a small MXEvent instance with typically value for `type` and 'content' fields.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// var mClearEvent: Event? = null
|
|
||||||
// private set
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Curve25519 key which we believe belongs to the sender of the event.
|
|
||||||
// * See `senderKey` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mSenderCurve25519Key: String? = null
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
|
|
||||||
// * See `claimedEd25519Key` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mClaimedEd25519Key: String? = null
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
|
||||||
// * See `forwardingCurve25519KeyChain` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Decryption error
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// var mCryptoError: MXCryptoError? = null
|
|
||||||
// private set
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this event is encrypted.
|
* @return true if this event is encrypted.
|
||||||
*/
|
*/
|
||||||
@ -142,51 +111,11 @@ data class Event(
|
|||||||
return TextUtils.equals(type, EventType.ENCRYPTED)
|
return TextUtils.equals(type, EventType.ENCRYPTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the clear data on this event.
|
|
||||||
* This is used after decrypting an event; it should not be used by applications.
|
|
||||||
*
|
|
||||||
* @param decryptionResult the decryption result, including the plaintext and some key info.
|
|
||||||
*/
|
|
||||||
// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
|
|
||||||
// mClearEvent = null
|
|
||||||
// if (decryptionResult != null) {
|
|
||||||
// if (decryptionResult.clearEvent != null) {
|
|
||||||
// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
|
||||||
// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
|
||||||
//
|
|
||||||
// if (mClearEvent != null) {
|
|
||||||
// mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
|
||||||
// mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
|
||||||
// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
|
||||||
//
|
|
||||||
// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
|
||||||
// // in the clear event
|
|
||||||
// try {
|
|
||||||
// content?.get("m.relates_to")?.let { clearRelates ->
|
|
||||||
// mClearEvent = mClearEvent?.copy(
|
|
||||||
// content = HashMap(mClearEvent!!.content).apply {
|
|
||||||
// this["m.relates_to"] = clearRelates
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// mCryptoError = null
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The curve25519 key that sent this event.
|
* @return The curve25519 key that sent this event.
|
||||||
*/
|
*/
|
||||||
fun getSenderKey(): String? {
|
fun getSenderKey(): String? {
|
||||||
return mxDecryptionResult?.senderKey
|
return mxDecryptionResult?.senderKey
|
||||||
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,23 +123,13 @@ data class Event(
|
|||||||
*/
|
*/
|
||||||
fun getKeysClaimed(): Map<String, String> {
|
fun getKeysClaimed(): Map<String, String> {
|
||||||
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
||||||
// val res = HashMap<String, String>()
|
|
||||||
//
|
|
||||||
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
|
|
||||||
//
|
|
||||||
// if (null != claimedEd25519Key) {
|
|
||||||
// res["ed25519"] = claimedEd25519Key
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return res
|
|
||||||
}
|
}
|
||||||
//
|
|
||||||
/**
|
/**
|
||||||
* @return the event type
|
* @return the event type
|
||||||
*/
|
*/
|
||||||
fun getClearType(): String {
|
fun getClearType(): String {
|
||||||
return mxDecryptionResult?.payload?.get("type")?.toString()
|
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
|
||||||
?: type//get("type")?.toString() ?: type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,30 +139,8 @@ data class Event(
|
|||||||
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return the linked crypto error
|
|
||||||
// */
|
|
||||||
// fun getCryptoError(): MXCryptoError? {
|
|
||||||
// return mCryptoError
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Update the linked crypto error
|
|
||||||
// *
|
|
||||||
// * @param error the new crypto error.
|
|
||||||
// */
|
|
||||||
// fun setCryptoError(error: MXCryptoError?) {
|
|
||||||
// mCryptoError = error
|
|
||||||
// if (null != error) {
|
|
||||||
// mClearEvent = null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
fun toContentStringWithIndent(): String {
|
fun toContentStringWithIndent(): String {
|
||||||
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
|
val contentMap = toContent()?.toMutableMap() ?: HashMap()
|
||||||
contentMap.remove("mxDecryptionResult")
|
|
||||||
contentMap.remove("mCryptoError")
|
|
||||||
return JSONObject(contentMap).toString(4)
|
return JSONObject(contentMap).toString(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,31 +199,19 @@ data class Event(
|
|||||||
|
|
||||||
|
|
||||||
fun Event.isTextMessage(): Boolean {
|
fun Event.isTextMessage(): Boolean {
|
||||||
if (this.getClearType() == EventType.MESSAGE) {
|
return getClearType() == EventType.MESSAGE
|
||||||
return getClearContent()?.toModel<MessageContent>()?.let {
|
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||||
when (it.type) {
|
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_NOTICE -> {
|
MessageType.MSGTYPE_NOTICE -> true
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Event.isImageMessage(): Boolean {
|
fun Event.isImageMessage(): Boolean {
|
||||||
if (this.getClearType() == EventType.MESSAGE) {
|
return getClearType() == EventType.MESSAGE
|
||||||
return getClearContent()?.toModel<MessageContent>()?.let {
|
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||||
when (it.type) {
|
MessageType.MSGTYPE_IMAGE -> true
|
||||||
MessageType.MSGTYPE_IMAGE -> {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ internal interface SessionParamsStore {
|
|||||||
|
|
||||||
fun getAll(): List<SessionParams>
|
fun getAll(): List<SessionParams>
|
||||||
|
|
||||||
fun save(sessionParams: SessionParams): Try<SessionParams>
|
fun save(sessionParams: SessionParams): Try<Unit>
|
||||||
|
|
||||||
fun delete(userId: String): Try<Unit>
|
fun delete(userId: String): Try<Unit>
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
|||||||
return sessionParams
|
return sessionParams
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save(sessionParams: SessionParams): Try<SessionParams> {
|
override fun save(sessionParams: SessionParams): Try<Unit> {
|
||||||
return Try {
|
return Try {
|
||||||
val entity = mapper.map(sessionParams)
|
val entity = mapper.map(sessionParams)
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
@ -72,7 +72,6 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
|||||||
}
|
}
|
||||||
realm.close()
|
realm.close()
|
||||||
}
|
}
|
||||||
sessionParams
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,3 +31,11 @@ inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
|
|||||||
fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
|
fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
|
||||||
{ callback.onFailure(it) },
|
{ callback.onFailure(it) },
|
||||||
{ callback.onSuccess(it) })
|
{ callback.onSuccess(it) })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as doOnNext for Observables
|
||||||
|
*/
|
||||||
|
inline fun <A> Try<A>.alsoDo(f: (A) -> Unit) = map {
|
||||||
|
f(it)
|
||||||
|
it
|
||||||
|
}
|
@ -115,7 +115,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
eventId,
|
eventId,
|
||||||
reason)
|
reason)
|
||||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
|
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editTextMessage(targetEventId: String,
|
override fun editTextMessage(targetEventId: String,
|
||||||
@ -199,14 +199,13 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
// Same parameter
|
// Same parameter
|
||||||
val params = EncryptEventWorker.Params(credentials.userId, roomId, event, keepKeys)
|
val params = EncryptEventWorker.Params(credentials.userId, roomId, event, keepKeys)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData)
|
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
val workRequest = TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData)
|
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, true)
|
||||||
return workRequest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
|
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
|
||||||
|
@ -22,10 +22,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
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.isTextMessage
|
|
||||||
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.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
@ -41,9 +38,11 @@ import im.vector.matrix.android.internal.database.query.where
|
|||||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
|
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
|
||||||
import im.vector.matrix.android.internal.util.CancelableWork
|
import im.vector.matrix.android.internal.util.CancelableWork
|
||||||
|
import im.vector.matrix.android.internal.worker.AlwaysSuccessfulWorker
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
|
import im.vector.matrix.android.internal.worker.startChain
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -82,11 +81,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||||
Timber.v("Send event in encrypted room")
|
Timber.v("Send event in encrypted room")
|
||||||
val encryptWork = createEncryptEventWork(event, true)
|
val encryptWork = createEncryptEventWork(event, true)
|
||||||
val sendWork = createSendEventWork(event)
|
val sendWork = createSendEventWork(event, false)
|
||||||
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, sendWork)
|
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, sendWork)
|
||||||
CancelableWork(context, encryptWork.id)
|
CancelableWork(context, encryptWork.id)
|
||||||
} else {
|
} else {
|
||||||
val sendWork = createSendEventWork(event)
|
val sendWork = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, sendWork)
|
TimelineSendEventWorkCommon.postWork(context, roomId, sendWork)
|
||||||
CancelableWork(context, sendWork.id)
|
CancelableWork(context, sendWork.id)
|
||||||
}
|
}
|
||||||
@ -106,7 +105,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
||||||
if (localEcho.root.isTextMessage()) {
|
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||||
return sendEvent(localEcho.root)
|
return sendEvent(localEcho.root)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -114,6 +113,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
|
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
|
||||||
|
if (localEcho.root.isImageMessage() && localEcho.root.sendState.hasFailed()) {
|
||||||
//TODO this need a refactoring of attachement sending
|
//TODO this need a refactoring of attachement sending
|
||||||
// val clearContent = localEcho.root.getClearContent()
|
// val clearContent = localEcho.root.getClearContent()
|
||||||
// val messageContent = clearContent?.toModel<MessageContent>() ?: return null
|
// val messageContent = clearContent?.toModel<MessageContent>() ?: return null
|
||||||
@ -144,7 +144,8 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||||
@ -162,15 +163,16 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
|
|
||||||
override fun clearSendingQueue() {
|
override fun clearSendingQueue() {
|
||||||
TimelineSendEventWorkCommon.cancelAllWorks(context, roomId)
|
TimelineSendEventWorkCommon.cancelAllWorks(context, roomId)
|
||||||
WorkManager.getInstance(context).cancelUniqueWork(buildWorkIdentifier(UPLOAD_WORK))
|
WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
||||||
|
|
||||||
matrixOneTimeWorkRequestBuilder<FakeSendWorker>()
|
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
||||||
|
matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
||||||
.build().let {
|
.build().let {
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, it, ExistingWorkPolicy.REPLACE)
|
TimelineSendEventWorkCommon.postWork(context, roomId, it, ExistingWorkPolicy.REPLACE)
|
||||||
|
|
||||||
//need to clear also image sending queue
|
//need to clear also image sending queue
|
||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,26 +246,26 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
||||||
|
|
||||||
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
||||||
val sendWork = createSendEventWork(localEcho)
|
val sendWork = createSendEventWork(localEcho, false)
|
||||||
|
|
||||||
if (isRoomEncrypted) {
|
if (isRoomEncrypted) {
|
||||||
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
||||||
|
|
||||||
val op: Operation = WorkManager.getInstance(context)
|
val op: Operation = WorkManager.getInstance(context)
|
||||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||||
.then(encryptWork)
|
.then(encryptWork)
|
||||||
.then(sendWork)
|
.then(sendWork)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
op.result.addListener(Runnable {
|
op.result.addListener(Runnable {
|
||||||
if (op.result.isCancelled) {
|
if (op.result.isCancelled) {
|
||||||
Timber.e("CHAINE WAS CANCELLED")
|
Timber.e("CHAIN WAS CANCELLED")
|
||||||
} else if (op.state.value is Operation.State.FAILURE) {
|
} else if (op.state.value is Operation.State.FAILURE) {
|
||||||
Timber.e("CHAINE DID FAIL")
|
Timber.e("CHAIN DID FAIL")
|
||||||
}
|
}
|
||||||
}, workerFutureListenerExecutor)
|
}, workerFutureListenerExecutor)
|
||||||
} else {
|
} else {
|
||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||||
.then(sendWork)
|
.then(sendWork)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
@ -275,11 +277,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
localEchoEventFactory.saveLocalEcho(monarchy, event)
|
localEchoEventFactory.saveLocalEcho(monarchy, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildWorkIdentifier(identifier: String): String {
|
private fun buildWorkName(identifier: String): String {
|
||||||
return "${roomId}_$identifier"
|
return "${roomId}_$identifier"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean = false): OneTimeWorkRequest {
|
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
// Same parameter
|
// Same parameter
|
||||||
val params = EncryptEventWorker.Params(credentials.userId, roomId, event)
|
val params = EncryptEventWorker.Params(credentials.userId, roomId, event)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
@ -287,20 +289,16 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
.setConstraints(WorkManagerUtil.workConstraints)
|
.setConstraints(WorkManagerUtil.workConstraints)
|
||||||
.setInputData(sendWorkData)
|
.setInputData(sendWorkData)
|
||||||
.apply {
|
.startChain(startChain)
|
||||||
if (startChain) {
|
|
||||||
setInputMerger(NoMerger::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData)
|
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
||||||
@ -309,23 +307,19 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
||||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
|
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUploadMediaWork(event: Event,
|
private fun createUploadMediaWork(event: Event,
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
isRoomEncrypted: Boolean,
|
isRoomEncrypted: Boolean,
|
||||||
startChain: Boolean = false): OneTimeWorkRequest {
|
startChain: Boolean): OneTimeWorkRequest {
|
||||||
val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment, isRoomEncrypted)
|
val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment, isRoomEncrypted)
|
||||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||||
|
|
||||||
return matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
return matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||||
.setConstraints(WorkManagerUtil.workConstraints)
|
.setConstraints(WorkManagerUtil.workConstraints)
|
||||||
.apply {
|
.startChain(startChain)
|
||||||
if (startChain) {
|
|
||||||
setInputMerger(NoMerger::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setInputData(uploadWorkData)
|
.setInputData(uploadWorkData)
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
@ -18,7 +18,10 @@ package im.vector.matrix.android.internal.session.room.send
|
|||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.InputMerger
|
import androidx.work.InputMerger
|
||||||
|
|
||||||
class NoMerger : InputMerger() {
|
/**
|
||||||
|
* InputMerger which takes only the first input, to ensure an appended work will only have the specified parameters
|
||||||
|
*/
|
||||||
|
internal class NoMerger : InputMerger() {
|
||||||
override fun merge(inputs: MutableList<Data>): Data {
|
override fun merge(inputs: MutableList<Data>): Data {
|
||||||
return inputs.first()
|
return inputs.first()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import android.content.Context
|
|||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||||
|
import im.vector.matrix.android.internal.worker.startChain
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ internal object TimelineSendEventWorkCommon {
|
|||||||
else -> {
|
else -> {
|
||||||
val firstWork = workRequests.first()
|
val firstWork = workRequests.first()
|
||||||
var continuation = WorkManager.getInstance(context)
|
var continuation = WorkManager.getInstance(context)
|
||||||
.beginUniqueWork(buildWorkIdentifier(roomId), ExistingWorkPolicy.APPEND, firstWork)
|
.beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork)
|
||||||
for (i in 1 until workRequests.size) {
|
for (i in 1 until workRequests.size) {
|
||||||
val workRequest = workRequests[i]
|
val workRequest = workRequests[i]
|
||||||
continuation = continuation.then(workRequest)
|
continuation = continuation.then(workRequest)
|
||||||
@ -53,23 +54,24 @@ internal object TimelineSendEventWorkCommon {
|
|||||||
|
|
||||||
fun postWork(context: Context, roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND) {
|
fun postWork(context: Context, roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND) {
|
||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.beginUniqueWork(buildWorkIdentifier(roomId), policy, workRequest)
|
.beginUniqueWork(buildWorkName(roomId), policy, workRequest)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified W : ListenableWorker> createWork(data: Data): OneTimeWorkRequest {
|
inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
|
||||||
return matrixOneTimeWorkRequestBuilder<W>()
|
return matrixOneTimeWorkRequestBuilder<W>()
|
||||||
.setConstraints(WorkManagerUtil.workConstraints)
|
.setConstraints(WorkManagerUtil.workConstraints)
|
||||||
|
.startChain(startChain)
|
||||||
.setInputData(data)
|
.setInputData(data)
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildWorkIdentifier(roomId: String): String {
|
private fun buildWorkName(roomId: String): String {
|
||||||
return "${roomId}_$SEND_WORK"
|
return "${roomId}_$SEND_WORK"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelAllWorks(context: Context, roomId: String) {
|
fun cancelAllWorks(context: Context, roomId: String) {
|
||||||
WorkManager.getInstance(context).cancelUniqueWork(buildWorkIdentifier(roomId))
|
WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(roomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,6 +36,6 @@ internal abstract class AccountDataModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAcountDataTask): UpdateUserAccountDataTask
|
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
||||||
|
|
||||||
}
|
}
|
@ -41,7 +41,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||||
private val credentials: Credentials) : UpdateUserAccountDataTask {
|
private val credentials: Credentials) : UpdateUserAccountDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
|
override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.worker
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
|
|
||||||
internal class FakeSendWorker(context: Context, params: WorkerParameters)
|
internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters)
|
||||||
: Worker(context, params) {
|
: Worker(context, params) {
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.worker
|
||||||
|
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.NoMerger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If startChain parameter is true, the builder will have a inputMerger set to [NoMerger]
|
||||||
|
*/
|
||||||
|
internal fun OneTimeWorkRequest.Builder.startChain(startChain: Boolean): OneTimeWorkRequest.Builder {
|
||||||
|
if (startChain) {
|
||||||
|
setInputMerger(NoMerger::class.java)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
@ -31,15 +31,14 @@ class ErrorFormatter @Inject constructor(val stringProvider: StringProvider) {
|
|||||||
|
|
||||||
fun toHumanReadable(throwable: Throwable?): String {
|
fun toHumanReadable(throwable: Throwable?): String {
|
||||||
return when (throwable) {
|
return when (throwable) {
|
||||||
null -> ""
|
null -> null
|
||||||
is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network)
|
is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network)
|
||||||
is Failure.ServerError -> {
|
is Failure.ServerError -> {
|
||||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||||
?: stringProvider.getString(R.string.unknown_error)
|
|
||||||
}
|
}
|
||||||
else -> throwable.localizedMessage
|
else -> throwable.localizedMessage
|
||||||
|
}
|
||||||
?: stringProvider.getString(R.string.unknown_error)
|
?: stringProvider.getString(R.string.unknown_error)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@ -18,25 +18,22 @@ package im.vector.riotx.core.extensions
|
|||||||
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
||||||
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
|
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
|
||||||
|
|
||||||
addTextChangedListener(object : TextWatcher {
|
addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
override fun afterTextChanged(editable: Editable?) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
val clearIcon = if (editable?.isNotEmpty() == true) clearIconRes else 0
|
val clearIcon = if (s.isNotEmpty()) clearIconRes else 0
|
||||||
setCompoundDrawablesWithIntrinsicBounds(searchIconRes, 0, clearIcon, 0)
|
setCompoundDrawablesWithIntrinsicBounds(searchIconRes, 0, clearIcon, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
|
||||||
})
|
})
|
||||||
|
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.riotx.core.platform
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -46,7 +47,7 @@ class ConfigurationViewModel @Inject constructor(
|
|||||||
if (newHash != currentConfigurationValue) {
|
if (newHash != currentConfigurationValue) {
|
||||||
Timber.v("Configuration: recreate the Activity")
|
Timber.v("Configuration: recreate the Activity")
|
||||||
currentConfigurationValue = newHash
|
currentConfigurationValue = newHash
|
||||||
_activityRestarter.postValue(LiveEvent(Unit))
|
_activityRestarter.postLiveEvent(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.riotx.core.platform
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TextWatcher with default no op implementation
|
* TextWatcher with default no op implementation
|
||||||
*/
|
*/
|
||||||
|
@ -24,11 +24,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.*
|
||||||
import com.airbnb.mvrx.Fail
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.viewModel
|
|
||||||
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
|
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
@ -39,7 +35,6 @@ import im.vector.riotx.core.extensions.observeEvent
|
|||||||
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||||
import im.vector.riotx.core.platform.WaitingViewData
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
import kotlinx.android.synthetic.main.activity.*
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
@ -98,7 +93,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||||||
} else
|
} else
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setMessage(errorFormatter.toHumanReadable(error))
|
.setMessage(errorFormatter.toHumanReadable(error))
|
||||||
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ package im.vector.riotx.features.home.createdirect
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
@ -33,10 +35,8 @@ import im.vector.matrix.rx.rx
|
|||||||
import im.vector.riotx.core.extensions.postLiveEvent
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private typealias KnowUsersFilter = String
|
private typealias KnowUsersFilter = String
|
||||||
@ -103,7 +103,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
|||||||
.execute {
|
.execute {
|
||||||
copy(createAndInviteState = it)
|
copy(createAndInviteState = it)
|
||||||
}
|
}
|
||||||
.disposeOnClear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { state ->
|
private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { state ->
|
||||||
|
@ -300,8 +300,9 @@ class RoomDetailFragment :
|
|||||||
composerLayout.collapse()
|
composerLayout.collapse()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enterSpecialMode(event: TimelineEvent, @DrawableRes
|
private fun enterSpecialMode(event: TimelineEvent,
|
||||||
iconRes: Int, useText: Boolean) {
|
@DrawableRes iconRes: Int,
|
||||||
|
useText: Boolean) {
|
||||||
commandAutocompletePolicy.enabled = false
|
commandAutocompletePolicy.enabled = false
|
||||||
//switch to expanded bar
|
//switch to expanded bar
|
||||||
composerLayout.composerRelatedMessageTitle.apply {
|
composerLayout.composerRelatedMessageTitle.apply {
|
||||||
@ -820,36 +821,31 @@ class RoomDetailFragment :
|
|||||||
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
|
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleActions(actionData: ActionsHandler.ActionData) {
|
private fun handleActions(action: SimpleAction) {
|
||||||
when (actionData.actionId) {
|
when (action) {
|
||||||
MessageMenuViewModel.ACTION_ADD_REACTION -> {
|
is SimpleAction.AddReaction -> {
|
||||||
val eventId = actionData.data?.toString() ?: return
|
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
||||||
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_VIEW_REACTIONS -> {
|
is SimpleAction.ViewReactions -> {
|
||||||
val messageInformationData = actionData.data as? MessageInformationData
|
ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
|
||||||
?: return
|
|
||||||
ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData)
|
|
||||||
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
|
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_COPY -> {
|
is SimpleAction.Copy -> {
|
||||||
//I need info about the current selected message :/
|
//I need info about the current selected message :/
|
||||||
copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
|
copyToClipboard(requireContext(), action.content, false)
|
||||||
val msg = requireContext().getString(R.string.copied_to_clipboard)
|
val msg = requireContext().getString(R.string.copied_to_clipboard)
|
||||||
showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
|
showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_DELETE -> {
|
is SimpleAction.Delete -> {
|
||||||
val eventId = actionData.data?.toString() ?: return
|
roomDetailViewModel.process(RoomDetailActions.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
||||||
roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_SHARE -> {
|
is SimpleAction.Share -> {
|
||||||
//TODO current data communication is too limited
|
//TODO current data communication is too limited
|
||||||
//Need to now the media type
|
//Need to now the media type
|
||||||
actionData.data?.toString()?.let {
|
|
||||||
//TODO bad, just POC
|
//TODO bad, just POC
|
||||||
BigImageViewer.imageLoader().loadImage(
|
BigImageViewer.imageLoader().loadImage(
|
||||||
actionData.hashCode(),
|
action.hashCode(),
|
||||||
Uri.parse(it),
|
Uri.parse(action.imageUrl),
|
||||||
object : ImageLoader.Callback {
|
object : ImageLoader.Callback {
|
||||||
override fun onFinish() {}
|
override fun onFinish() {}
|
||||||
|
|
||||||
@ -869,57 +865,57 @@ class RoomDetailFragment :
|
|||||||
override fun onStart() {}
|
override fun onStart() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
is SimpleAction.ViewSource -> {
|
||||||
MessageMenuViewModel.VIEW_SOURCE,
|
|
||||||
MessageMenuViewModel.VIEW_DECRYPTED_SOURCE -> {
|
|
||||||
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
||||||
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
||||||
it.text = actionData.data?.toString() ?: ""
|
it.text = action.content
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_QUICK_REACT -> {
|
is SimpleAction.ViewDecryptedSource -> {
|
||||||
|
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
||||||
|
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
||||||
|
it.text = action.content
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
is SimpleAction.QuickReact -> {
|
||||||
//eventId,ClickedOn,Add
|
//eventId,ClickedOn,Add
|
||||||
(actionData.data as? Triple<String, String, Boolean>)?.let { (eventId, clickedOn, add) ->
|
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
|
||||||
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, add))
|
|
||||||
}
|
}
|
||||||
|
is SimpleAction.Edit -> {
|
||||||
|
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(action.eventId))
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_EDIT -> {
|
is SimpleAction.Quote -> {
|
||||||
val eventId = actionData.data.toString()
|
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(action.eventId))
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_QUOTE -> {
|
is SimpleAction.Reply -> {
|
||||||
val eventId = actionData.data.toString()
|
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(action.eventId))
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_REPLY -> {
|
is SimpleAction.CopyPermalink -> {
|
||||||
val eventId = actionData.data.toString()
|
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
|
||||||
}
|
|
||||||
MessageMenuViewModel.ACTION_COPY_PERMALINK -> {
|
|
||||||
val eventId = actionData.data.toString()
|
|
||||||
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, eventId)
|
|
||||||
copyToClipboard(requireContext(), permalink, false)
|
copyToClipboard(requireContext(), permalink, false)
|
||||||
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||||
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_RESEND -> {
|
is SimpleAction.Resend -> {
|
||||||
val eventId = actionData.data.toString()
|
roomDetailViewModel.process(RoomDetailActions.ResendMessage(action.eventId))
|
||||||
roomDetailViewModel.process(RoomDetailActions.ResendMessage(eventId))
|
|
||||||
}
|
}
|
||||||
MessageMenuViewModel.ACTION_REMOVE -> {
|
is SimpleAction.Remove -> {
|
||||||
val eventId = actionData.data.toString()
|
roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(action.eventId))
|
||||||
roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(eventId))
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
|
Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
|
|||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -25,15 +26,10 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
class ActionsHandler @Inject constructor() : ViewModel() {
|
class ActionsHandler @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
data class ActionData(
|
val actionCommandEvent = MutableLiveData<LiveEvent<SimpleAction>>()
|
||||||
val actionId: String,
|
|
||||||
val data: Any?
|
|
||||||
)
|
|
||||||
|
|
||||||
val actionCommandEvent = MutableLiveData<LiveEvent<ActionData>>()
|
fun fireAction(action: SimpleAction) {
|
||||||
|
actionCommandEvent.postLiveEvent(action)
|
||||||
fun fireAction(actionId: String, data: Any? = null) {
|
|
||||||
actionCommandEvent.value = LiveEvent(ActionData(actionId,data))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -89,7 +89,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
menuActionFragment.interactionListener = object : MessageMenuFragment.InteractionListener {
|
menuActionFragment.interactionListener = object : MessageMenuFragment.InteractionListener {
|
||||||
override fun didSelectMenuAction(simpleAction: SimpleAction) {
|
override fun didSelectMenuAction(simpleAction: SimpleAction) {
|
||||||
actionHandlerModel.fireAction(simpleAction.uid, simpleAction.data)
|
actionHandlerModel.fireAction(simpleAction)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
|
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
|
||||||
|
|
||||||
override fun didQuickReactWith(clickedOn: String, add: Boolean, eventId: String) {
|
override fun didQuickReactWith(clickedOn: String, add: Boolean, eventId: String) {
|
||||||
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clickedOn, add))
|
actionHandlerModel.fireAction(SimpleAction.QuickReact(eventId, clickedOn, add))
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.*
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
@ -36,7 +38,24 @@ import im.vector.riotx.core.utils.isSingleEmoji
|
|||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
|
||||||
|
|
||||||
data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
|
sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) {
|
||||||
|
data class AddReaction(val eventId: String) : SimpleAction(R.string.message_add_reaction, R.drawable.ic_add_reaction)
|
||||||
|
data class Copy(val content: String) : SimpleAction(R.string.copy, R.drawable.ic_copy)
|
||||||
|
data class Edit(val eventId: String) : SimpleAction(R.string.edit, R.drawable.ic_edit)
|
||||||
|
data class Quote(val eventId: String) : SimpleAction(R.string.quote, R.drawable.ic_quote)
|
||||||
|
data class Reply(val eventId: String) : SimpleAction(R.string.reply, R.drawable.ic_reply)
|
||||||
|
data class Share(val imageUrl: String?) : SimpleAction(R.string.share, R.drawable.ic_share)
|
||||||
|
data class Resend(val eventId: String) : SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw)
|
||||||
|
data class Remove(val eventId: String) : SimpleAction(R.string.remove, R.drawable.ic_trash)
|
||||||
|
data class Delete(val eventId: String) : SimpleAction(R.string.delete, R.drawable.ic_delete)
|
||||||
|
data class Cancel(val eventId: String) : SimpleAction(R.string.cancel, R.drawable.ic_close_round)
|
||||||
|
data class ViewSource(val content: String) : SimpleAction(R.string.view_source, R.drawable.ic_view_source)
|
||||||
|
data class ViewDecryptedSource(val content: String) : SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source)
|
||||||
|
data class CopyPermalink(val eventId: String) : SimpleAction(R.string.permalink, R.drawable.ic_permalink)
|
||||||
|
data class Flag(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag)
|
||||||
|
data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0)
|
||||||
|
data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions)
|
||||||
|
}
|
||||||
|
|
||||||
data class MessageMenuState(
|
data class MessageMenuState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
@ -68,24 +87,6 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
private val informationData: MessageInformationData = initialState.informationData
|
private val informationData: MessageInformationData = initialState.informationData
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> {
|
companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> {
|
||||||
|
|
||||||
const val ACTION_ADD_REACTION = "add_reaction"
|
|
||||||
const val ACTION_COPY = "copy"
|
|
||||||
const val ACTION_EDIT = "edit"
|
|
||||||
const val ACTION_QUOTE = "quote"
|
|
||||||
const val ACTION_REPLY = "reply"
|
|
||||||
const val ACTION_SHARE = "share"
|
|
||||||
const val ACTION_RESEND = "resend"
|
|
||||||
const val ACTION_REMOVE = "remove"
|
|
||||||
const val ACTION_DELETE = "delete"
|
|
||||||
const val ACTION_CANCEL = "cancel"
|
|
||||||
const val VIEW_SOURCE = "VIEW_SOURCE"
|
|
||||||
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
|
|
||||||
const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK"
|
|
||||||
const val ACTION_FLAG = "ACTION_FLAG"
|
|
||||||
const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
|
|
||||||
const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS"
|
|
||||||
|
|
||||||
override fun create(viewModelContext: ViewModelContext, state: MessageMenuState): MessageMenuViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: MessageMenuState): MessageMenuViewModel? {
|
||||||
val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.messageMenuViewModelFactory.create(state)
|
return fragment.messageMenuViewModelFactory.create(state)
|
||||||
@ -99,75 +100,64 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
private fun observeEvent() {
|
private fun observeEvent() {
|
||||||
RxRoom(room)
|
RxRoom(room)
|
||||||
.liveTimelineEvent(eventId)
|
.liveTimelineEvent(eventId)
|
||||||
?.map {
|
.map {
|
||||||
actionsForEvent(it)
|
actionsForEvent(it)
|
||||||
}
|
}
|
||||||
?.execute {
|
.execute {
|
||||||
copy(actions = it)
|
copy(actions = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(event: TimelineEvent): List<SimpleAction> {
|
private fun actionsForEvent(event: TimelineEvent): List<SimpleAction> {
|
||||||
|
|
||||||
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
|
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: event.root.getClearContent().toModel()
|
?: event.root.getClearContent().toModel()
|
||||||
val type = messageContent?.type
|
val type = messageContent?.type
|
||||||
|
|
||||||
return if (event.root.sendState.hasFailed()) {
|
return arrayListOf<SimpleAction>().apply {
|
||||||
arrayListOf<SimpleAction>().apply {
|
if (event.root.sendState.hasFailed()) {
|
||||||
if (canRetry(event)) {
|
if (canRetry(event)) {
|
||||||
this.add(SimpleAction(ACTION_RESEND, R.string.global_retry, R.drawable.ic_refresh_cw, eventId))
|
add(SimpleAction.Resend(eventId))
|
||||||
}
|
|
||||||
this.add(SimpleAction(ACTION_REMOVE, R.string.remove, R.drawable.ic_trash, eventId))
|
|
||||||
}
|
}
|
||||||
|
add(SimpleAction.Remove(eventId))
|
||||||
} else if (event.root.sendState.isSending()) {
|
} else if (event.root.sendState.isSending()) {
|
||||||
//TODO is uploading attachment?
|
//TODO is uploading attachment?
|
||||||
arrayListOf<SimpleAction>().apply {
|
|
||||||
if (canCancel(event)) {
|
if (canCancel(event)) {
|
||||||
this.add(SimpleAction(ACTION_CANCEL, R.string.cancel, R.drawable.ic_close_round, eventId))
|
add(SimpleAction.Cancel(eventId))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
arrayListOf<SimpleAction>().apply {
|
|
||||||
|
|
||||||
if (!event.root.isRedacted()) {
|
if (!event.root.isRedacted()) {
|
||||||
|
|
||||||
if (canReply(event, messageContent)) {
|
if (canReply(event, messageContent)) {
|
||||||
add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId))
|
add(SimpleAction.Reply(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canEdit(event, session.myUserId)) {
|
if (canEdit(event, session.myUserId)) {
|
||||||
add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
|
add(SimpleAction.Edit(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canRedact(event, session.myUserId)) {
|
if (canRedact(event, session.myUserId)) {
|
||||||
add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
|
add(SimpleAction.Delete(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canCopy(type)) {
|
if (canCopy(type)) {
|
||||||
//TODO copy images? html? see ClipBoard
|
//TODO copy images? html? see ClipBoard
|
||||||
add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
|
add(SimpleAction.Copy(messageContent!!.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.canReact()) {
|
if (event.canReact()) {
|
||||||
add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId))
|
add(SimpleAction.AddReaction(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canQuote(event, messageContent)) {
|
if (canQuote(event, messageContent)) {
|
||||||
add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId))
|
add(SimpleAction.Quote(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canViewReactions(event)) {
|
if (canViewReactions(event)) {
|
||||||
add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData))
|
add(SimpleAction.ViewReactions(informationData))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canShare(type)) {
|
if (canShare(type)) {
|
||||||
if (messageContent is MessageImageContent) {
|
if (messageContent is MessageImageContent) {
|
||||||
add(
|
add(SimpleAction.Share(session.contentUrlResolver().resolveFullSize(messageContent.url)))
|
||||||
SimpleAction(ACTION_SHARE,
|
|
||||||
R.string.share, R.drawable.ic_share,
|
|
||||||
session.contentUrlResolver().resolveFullSize(messageContent.url))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
@ -181,17 +171,17 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, event.root.toContentStringWithIndent()))
|
add(SimpleAction.ViewSource(event.root.toContentStringWithIndent()))
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
val decryptedContent = event.root.toClearContentStringWithIndent()
|
val decryptedContent = event.root.toClearContentStringWithIndent()
|
||||||
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
||||||
add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
|
add(SimpleAction.ViewDecryptedSource(decryptedContent))
|
||||||
}
|
}
|
||||||
add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
|
add(SimpleAction.CopyPermalink(eventId))
|
||||||
|
|
||||||
if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) {
|
if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) {
|
||||||
//not sent by me
|
//not sent by me
|
||||||
add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId))
|
add(SimpleAction.Flag(eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,9 +259,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
MessageType.MSGTYPE_NOTICE,
|
MessageType.MSGTYPE_NOTICE,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.FORMAT_MATRIX_HTML,
|
MessageType.FORMAT_MATRIX_HTML,
|
||||||
MessageType.MSGTYPE_LOCATION -> {
|
MessageType.MSGTYPE_LOCATION -> true
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,9 +269,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
return when (type) {
|
return when (type) {
|
||||||
MessageType.MSGTYPE_IMAGE,
|
MessageType.MSGTYPE_IMAGE,
|
||||||
MessageType.MSGTYPE_AUDIO,
|
MessageType.MSGTYPE_AUDIO,
|
||||||
MessageType.MSGTYPE_VIDEO -> {
|
MessageType.MSGTYPE_VIDEO -> true
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,11 +166,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
root.isClickable = informationData.sendState.isSent()
|
root.isClickable = informationData.sendState.isSent()
|
||||||
val state = if (informationData.hasPendingEdits) SendState.UNSENT else informationData.sendState
|
val state = if (informationData.hasPendingEdits) SendState.UNSENT else informationData.sendState
|
||||||
textView?.setTextColor(colorProvider.getMessageTextColor(state))
|
textView?.setTextColor(colorProvider.getMessageTextColor(state))
|
||||||
failureIndicator?.isVisible = when (informationData.sendState) {
|
failureIndicator?.isVisible = informationData.sendState.hasFailed()
|
||||||
SendState.UNDELIVERED,
|
|
||||||
SendState.FAILED_UNKNOWN_DEVICES -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
||||||
|
@ -53,10 +53,6 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
|||||||
holder.mediaContentView.setOnLongClickListener(longClickListener)
|
holder.mediaContentView.setOnLongClickListener(longClickListener)
|
||||||
// The sending state color will be apply to the progress text
|
// The sending state color will be apply to the progress text
|
||||||
renderSendState(holder.imageView, null, holder.failedToSendIndicator)
|
renderSendState(holder.imageView, null, holder.failedToSendIndicator)
|
||||||
holder.progressLayout
|
|
||||||
if (informationData.sendState.hasFailed()) {
|
|
||||||
|
|
||||||
}
|
|
||||||
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
|||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
|
private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
|
||||||
_openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId))
|
_openRoomLiveData.postLiveEvent(action.roomSummary.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
|
private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
|
||||||
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
|||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
|
import im.vector.riotx.core.glide.GlideRequest
|
||||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -67,27 +68,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
imageView.layoutParams.height = height
|
imageView.layoutParams.height = height
|
||||||
imageView.layoutParams.width = width
|
imageView.layoutParams.width = width
|
||||||
|
|
||||||
val glideRequest = if (data.elementToDecrypt != null) {
|
createGlideRequest(data, mode, imageView, width, height)
|
||||||
// Encrypted image
|
|
||||||
GlideApp
|
|
||||||
.with(imageView)
|
|
||||||
.load(data)
|
|
||||||
} else {
|
|
||||||
// Clear image
|
|
||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
|
||||||
val resolvedUrl = when (mode) {
|
|
||||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
|
||||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
|
||||||
}
|
|
||||||
//Fallback to base url
|
|
||||||
?: data.url
|
|
||||||
|
|
||||||
GlideApp
|
|
||||||
.with(imageView)
|
|
||||||
.load(resolvedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
glideRequest
|
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
||||||
.thumbnail(0.3f)
|
.thumbnail(0.3f)
|
||||||
@ -95,31 +76,11 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback :((Boolean) -> Unit)? = null) {
|
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) {
|
||||||
val (width, height) = processSize(data, mode)
|
val (width, height) = processSize(data, mode)
|
||||||
|
|
||||||
val glideRequest = if (data.elementToDecrypt != null) {
|
createGlideRequest(data, mode, imageView, width, height)
|
||||||
// Encrypted image
|
.listener(object : RequestListener<Drawable> {
|
||||||
GlideApp
|
|
||||||
.with(imageView)
|
|
||||||
.load(data)
|
|
||||||
} else {
|
|
||||||
// Clear image
|
|
||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
|
||||||
val resolvedUrl = when (mode) {
|
|
||||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
|
||||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
|
||||||
}
|
|
||||||
//Fallback to base url
|
|
||||||
?: data.url
|
|
||||||
|
|
||||||
GlideApp
|
|
||||||
.with(imageView)
|
|
||||||
.load(resolvedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
glideRequest
|
|
||||||
.listener(object: RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(e: GlideException?,
|
override fun onLoadFailed(e: GlideException?,
|
||||||
model: Any?,
|
model: Any?,
|
||||||
target: Target<Drawable>?,
|
target: Target<Drawable>?,
|
||||||
@ -140,7 +101,28 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
})
|
})
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, width: Int, height: Int): GlideRequest<Drawable> {
|
||||||
|
return if (data.elementToDecrypt != null) {
|
||||||
|
// Encrypted image
|
||||||
|
GlideApp
|
||||||
|
.with(imageView)
|
||||||
|
.load(data)
|
||||||
|
} else {
|
||||||
|
// Clear image
|
||||||
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
|
val resolvedUrl = when (mode) {
|
||||||
|
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||||
|
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
|
}
|
||||||
|
//Fallback to base url
|
||||||
|
?: data.url
|
||||||
|
|
||||||
|
GlideApp
|
||||||
|
.with(imageView)
|
||||||
|
.load(resolvedUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(data: Data, imageView: BigImageView) {
|
fun render(data: Data, imageView: BigImageView) {
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<!-- enable window content transitions -->
|
<!-- enable window content transitions -->
|
||||||
<item name="android:windowContentTransitions">true</item>
|
<item name="android:windowContentTransitions">true</item>
|
||||||
|
|
||||||
|
|
||||||
<!-- specify shared element enter and exit transitions -->
|
<!-- specify shared element enter and exit transitions -->
|
||||||
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
|
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
|
||||||
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
|
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
|
||||||
|
Loading…
Reference in New Issue
Block a user