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
|
||||
}
|
||||
|
||||
val index = matrixId.lastIndexOf(":")
|
||||
val index = matrixId.indexOf(":")
|
||||
|
||||
return if (index == -1) {
|
||||
null
|
||||
|
@ -82,8 +82,13 @@ data class Event(
|
||||
) {
|
||||
|
||||
|
||||
@Transient
|
||||
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||
|
||||
@Transient
|
||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||
|
||||
@Transient
|
||||
var sendState: SendState = SendState.UNKNOWN
|
||||
|
||||
|
||||
@ -99,42 +104,6 @@ data class Event(
|
||||
// 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.
|
||||
*/
|
||||
@ -142,51 +111,11 @@ data class Event(
|
||||
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.
|
||||
*/
|
||||
fun getSenderKey(): String? {
|
||||
return mxDecryptionResult?.senderKey
|
||||
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,23 +123,13 @@ data class Event(
|
||||
*/
|
||||
fun getKeysClaimed(): Map<String, String> {
|
||||
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
|
||||
*/
|
||||
fun getClearType(): String {
|
||||
return mxDecryptionResult?.payload?.get("type")?.toString()
|
||||
?: type//get("type")?.toString() ?: type
|
||||
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
|
||||
}
|
||||
|
||||
/**
|
||||
@ -220,30 +139,8 @@ data class Event(
|
||||
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 {
|
||||
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
|
||||
contentMap.remove("mxDecryptionResult")
|
||||
contentMap.remove("mCryptoError")
|
||||
val contentMap = toContent()?.toMutableMap() ?: HashMap()
|
||||
return JSONObject(contentMap).toString(4)
|
||||
}
|
||||
|
||||
@ -302,31 +199,19 @@ data class Event(
|
||||
|
||||
|
||||
fun Event.isTextMessage(): Boolean {
|
||||
if (this.getClearType() == EventType.MESSAGE) {
|
||||
return getClearContent()?.toModel<MessageContent>()?.let {
|
||||
when (it.type) {
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_NOTICE -> {
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
} ?: false
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_NOTICE -> true
|
||||
else -> false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun Event.isImageMessage(): Boolean {
|
||||
if (this.getClearType() == EventType.MESSAGE) {
|
||||
return getClearContent()?.toModel<MessageContent>()?.let {
|
||||
when (it.type) {
|
||||
MessageType.MSGTYPE_IMAGE -> {
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
} ?: false
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
MessageType.MSGTYPE_IMAGE -> true
|
||||
else -> false
|
||||
}
|
||||
return false
|
||||
}
|
@ -27,7 +27,7 @@ internal interface SessionParamsStore {
|
||||
|
||||
fun getAll(): List<SessionParams>
|
||||
|
||||
fun save(sessionParams: SessionParams): Try<SessionParams>
|
||||
fun save(sessionParams: SessionParams): Try<Unit>
|
||||
|
||||
fun delete(userId: String): Try<Unit>
|
||||
|
||||
|
@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||
return sessionParams
|
||||
}
|
||||
|
||||
override fun save(sessionParams: SessionParams): Try<SessionParams> {
|
||||
override fun save(sessionParams: SessionParams): Try<Unit> {
|
||||
return Try {
|
||||
val entity = mapper.map(sessionParams)
|
||||
if (entity != null) {
|
||||
@ -72,7 +72,6 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||
}
|
||||
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(
|
||||
{ callback.onFailure(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,
|
||||
reason)
|
||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
|
||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||
}
|
||||
|
||||
override fun editTextMessage(targetEventId: String,
|
||||
@ -199,14 +199,13 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(credentials.userId, roomId, event, keepKeys)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData)
|
||||
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
val workRequest = TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData)
|
||||
return workRequest
|
||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, true)
|
||||
}
|
||||
|
||||
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.session.content.ContentAttachmentData
|
||||
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.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.events.model.*
|
||||
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.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.room.timeline.TimelineSendEventWorkCommon
|
||||
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.matrixOneTimeWorkRequestBuilder
|
||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||
import im.vector.matrix.android.internal.worker.startChain
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -82,11 +81,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||
Timber.v("Send event in encrypted room")
|
||||
val encryptWork = createEncryptEventWork(event, true)
|
||||
val sendWork = createSendEventWork(event)
|
||||
val sendWork = createSendEventWork(event, false)
|
||||
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, sendWork)
|
||||
CancelableWork(context, encryptWork.id)
|
||||
} else {
|
||||
val sendWork = createSendEventWork(event)
|
||||
val sendWork = createSendEventWork(event, true)
|
||||
TimelineSendEventWorkCommon.postWork(context, roomId, sendWork)
|
||||
CancelableWork(context, sendWork.id)
|
||||
}
|
||||
@ -106,7 +105,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
}
|
||||
|
||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
||||
if (localEcho.root.isTextMessage()) {
|
||||
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||
return sendEvent(localEcho.root)
|
||||
}
|
||||
return null
|
||||
@ -114,7 +113,8 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
}
|
||||
|
||||
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
|
||||
//TODO this need a refactoring of attachement sending
|
||||
if (localEcho.root.isImageMessage() && localEcho.root.sendState.hasFailed()) {
|
||||
//TODO this need a refactoring of attachement sending
|
||||
// val clearContent = localEcho.root.getClearContent()
|
||||
// val messageContent = clearContent?.toModel<MessageContent>() ?: return null
|
||||
// when (messageContent.type) {
|
||||
@ -143,8 +143,9 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return null
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||
@ -162,15 +163,16 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
|
||||
override fun clearSendingQueue() {
|
||||
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 {
|
||||
TimelineSendEventWorkCommon.postWork(context, roomId, it, ExistingWorkPolicy.REPLACE)
|
||||
|
||||
//need to clear also image sending queue
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
@ -244,26 +246,26 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
||||
|
||||
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
||||
val sendWork = createSendEventWork(localEcho)
|
||||
val sendWork = createSendEventWork(localEcho, false)
|
||||
|
||||
if (isRoomEncrypted) {
|
||||
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
||||
|
||||
val op: Operation = WorkManager.getInstance(context)
|
||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(encryptWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
op.result.addListener(Runnable {
|
||||
if (op.result.isCancelled) {
|
||||
Timber.e("CHAINE WAS CANCELLED")
|
||||
Timber.e("CHAIN WAS CANCELLED")
|
||||
} else if (op.state.value is Operation.State.FAILURE) {
|
||||
Timber.e("CHAINE DID FAIL")
|
||||
Timber.e("CHAIN DID FAIL")
|
||||
}
|
||||
}, workerFutureListenerExecutor)
|
||||
} else {
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
}
|
||||
@ -275,11 +277,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
localEchoEventFactory.saveLocalEcho(monarchy, event)
|
||||
}
|
||||
|
||||
private fun buildWorkIdentifier(identifier: String): String {
|
||||
private fun buildWorkName(identifier: String): String {
|
||||
return "${roomId}_$identifier"
|
||||
}
|
||||
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean = false): OneTimeWorkRequest {
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(credentials.userId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
@ -287,20 +289,16 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
.setConstraints(WorkManagerUtil.workConstraints)
|
||||
.setInputData(sendWorkData)
|
||||
.apply {
|
||||
if (startChain) {
|
||||
setInputMerger(NoMerger::class.java)
|
||||
}
|
||||
}
|
||||
.startChain(startChain)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
|
||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData)
|
||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
}
|
||||
|
||||
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 redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
|
||||
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||
}
|
||||
|
||||
private fun createUploadMediaWork(event: Event,
|
||||
attachment: ContentAttachmentData,
|
||||
isRoomEncrypted: Boolean,
|
||||
startChain: Boolean = false): OneTimeWorkRequest {
|
||||
startChain: Boolean): OneTimeWorkRequest {
|
||||
val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment, isRoomEncrypted)
|
||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||
|
||||
return matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||
.setConstraints(WorkManagerUtil.workConstraints)
|
||||
.apply {
|
||||
if (startChain) {
|
||||
setInputMerger(NoMerger::class.java)
|
||||
}
|
||||
}
|
||||
.startChain(startChain)
|
||||
.setInputData(uploadWorkData)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
@ -18,7 +18,10 @@ package im.vector.matrix.android.internal.session.room.send
|
||||
import androidx.work.Data
|
||||
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 {
|
||||
return inputs.first()
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import android.content.Context
|
||||
import androidx.work.*
|
||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||
import im.vector.matrix.android.internal.worker.startChain
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
@ -41,7 +42,7 @@ internal object TimelineSendEventWorkCommon {
|
||||
else -> {
|
||||
val firstWork = workRequests.first()
|
||||
var continuation = WorkManager.getInstance(context)
|
||||
.beginUniqueWork(buildWorkIdentifier(roomId), ExistingWorkPolicy.APPEND, firstWork)
|
||||
.beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork)
|
||||
for (i in 1 until workRequests.size) {
|
||||
val workRequest = workRequests[i]
|
||||
continuation = continuation.then(workRequest)
|
||||
@ -53,23 +54,24 @@ internal object TimelineSendEventWorkCommon {
|
||||
|
||||
fun postWork(context: Context, roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND) {
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(buildWorkIdentifier(roomId), policy, workRequest)
|
||||
.beginUniqueWork(buildWorkName(roomId), policy, workRequest)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
inline fun <reified W : ListenableWorker> createWork(data: Data): OneTimeWorkRequest {
|
||||
inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
|
||||
return matrixOneTimeWorkRequestBuilder<W>()
|
||||
.setConstraints(WorkManagerUtil.workConstraints)
|
||||
.startChain(startChain)
|
||||
.setInputData(data)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildWorkIdentifier(roomId: String): String {
|
||||
private fun buildWorkName(roomId: String): String {
|
||||
return "${roomId}_$SEND_WORK"
|
||||
}
|
||||
|
||||
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
|
||||
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAcountDataTask): UpdateUserAccountDataTask
|
||||
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
||||
|
||||
}
|
@ -41,8 +41,8 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
||||
|
||||
}
|
||||
|
||||
internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||
private val credentials: Credentials) : UpdateUserAccountDataTask {
|
||||
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||
private val credentials: Credentials) : UpdateUserAccountDataTask {
|
||||
|
||||
override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
|
||||
return executeRequest {
|
||||
|
@ -13,13 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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 androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
|
||||
internal class FakeSendWorker(context: Context, params: WorkerParameters)
|
||||
internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters)
|
||||
: Worker(context, params) {
|
||||
|
||||
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 {
|
||||
return when (throwable) {
|
||||
null -> ""
|
||||
null -> null
|
||||
is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network)
|
||||
is Failure.ServerError -> {
|
||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||
?: stringProvider.getString(R.string.unknown_error)
|
||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
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.InputType
|
||||
import android.text.TextWatcher
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.DrawableRes
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.SimpleTextWatcher
|
||||
|
||||
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
||||
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
|
||||
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
val clearIcon = if (editable?.isNotEmpty() == true) clearIconRes else 0
|
||||
addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val clearIcon = if (s.isNotEmpty()) clearIconRes else 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
|
||||
|
@ -19,6 +19,7 @@ package im.vector.riotx.core.platform
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||
import timber.log.Timber
|
||||
@ -46,7 +47,7 @@ class ConfigurationViewModel @Inject constructor(
|
||||
if (newHash != currentConfigurationValue) {
|
||||
Timber.v("Configuration: recreate the Activity")
|
||||
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.TextWatcher
|
||||
|
||||
|
||||
/**
|
||||
* TextWatcher with default no op implementation
|
||||
*/
|
||||
|
@ -24,11 +24,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
|
||||
import im.vector.riotx.R
|
||||
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.WaitingViewData
|
||||
import kotlinx.android.synthetic.main.activity.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||
@ -98,7 +93,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||
} else
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(errorFormatter.toHumanReadable(error))
|
||||
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,9 @@ package im.vector.riotx.features.home.createdirect
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
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.squareup.inject.assisted.Assisted
|
||||
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.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.BiFunction
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private typealias KnowUsersFilter = String
|
||||
@ -103,7 +103,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
.execute {
|
||||
copy(createAndInviteState = it)
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { state ->
|
||||
|
@ -300,8 +300,9 @@ class RoomDetailFragment :
|
||||
composerLayout.collapse()
|
||||
}
|
||||
|
||||
private fun enterSpecialMode(event: TimelineEvent, @DrawableRes
|
||||
iconRes: Int, useText: Boolean) {
|
||||
private fun enterSpecialMode(event: TimelineEvent,
|
||||
@DrawableRes iconRes: Int,
|
||||
useText: Boolean) {
|
||||
commandAutocompletePolicy.enabled = false
|
||||
//switch to expanded bar
|
||||
composerLayout.composerRelatedMessageTitle.apply {
|
||||
@ -820,106 +821,101 @@ class RoomDetailFragment :
|
||||
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
|
||||
}
|
||||
|
||||
private fun handleActions(actionData: ActionsHandler.ActionData) {
|
||||
when (actionData.actionId) {
|
||||
MessageMenuViewModel.ACTION_ADD_REACTION -> {
|
||||
val eventId = actionData.data?.toString() ?: return
|
||||
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
|
||||
private fun handleActions(action: SimpleAction) {
|
||||
when (action) {
|
||||
is SimpleAction.AddReaction -> {
|
||||
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
||||
}
|
||||
MessageMenuViewModel.ACTION_VIEW_REACTIONS -> {
|
||||
val messageInformationData = actionData.data as? MessageInformationData
|
||||
?: return
|
||||
ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData)
|
||||
is SimpleAction.ViewReactions -> {
|
||||
ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
|
||||
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
|
||||
}
|
||||
MessageMenuViewModel.ACTION_COPY -> {
|
||||
is SimpleAction.Copy -> {
|
||||
//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)
|
||||
showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
MessageMenuViewModel.ACTION_DELETE -> {
|
||||
val eventId = actionData.data?.toString() ?: return
|
||||
roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
||||
is SimpleAction.Delete -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_SHARE -> {
|
||||
is SimpleAction.Share -> {
|
||||
//TODO current data communication is too limited
|
||||
//Need to now the media type
|
||||
actionData.data?.toString()?.let {
|
||||
//TODO bad, just POC
|
||||
BigImageViewer.imageLoader().loadImage(
|
||||
actionData.hashCode(),
|
||||
Uri.parse(it),
|
||||
object : ImageLoader.Callback {
|
||||
override fun onFinish() {}
|
||||
|
||||
override fun onSuccess(image: File?) {
|
||||
if (image != null)
|
||||
shareMedia(requireContext(), image, "image/*")
|
||||
}
|
||||
|
||||
override fun onFail(error: Exception?) {}
|
||||
|
||||
override fun onCacheHit(imageType: Int, image: File?) {}
|
||||
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {}
|
||||
|
||||
override fun onProgress(progress: Int) {}
|
||||
|
||||
override fun onStart() {}
|
||||
//TODO bad, just POC
|
||||
BigImageViewer.imageLoader().loadImage(
|
||||
action.hashCode(),
|
||||
Uri.parse(action.imageUrl),
|
||||
object : ImageLoader.Callback {
|
||||
override fun onFinish() {}
|
||||
|
||||
override fun onSuccess(image: File?) {
|
||||
if (image != null)
|
||||
shareMedia(requireContext(), image, "image/*")
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
override fun onFail(error: Exception?) {}
|
||||
|
||||
override fun onCacheHit(imageType: Int, image: File?) {}
|
||||
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {}
|
||||
|
||||
override fun onProgress(progress: Int) {}
|
||||
|
||||
override fun onStart() {}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
MessageMenuViewModel.VIEW_SOURCE,
|
||||
MessageMenuViewModel.VIEW_DECRYPTED_SOURCE -> {
|
||||
is SimpleAction.ViewSource -> {
|
||||
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
||||
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
||||
it.text = actionData.data?.toString() ?: ""
|
||||
it.text = action.content
|
||||
}
|
||||
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setView(view)
|
||||
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
MessageMenuViewModel.ACTION_QUICK_REACT -> {
|
||||
//eventId,ClickedOn,Add
|
||||
(actionData.data as? Triple<String, String, Boolean>)?.let { (eventId, clickedOn, add) ->
|
||||
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, add))
|
||||
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()
|
||||
}
|
||||
MessageMenuViewModel.ACTION_EDIT -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
|
||||
is SimpleAction.QuickReact -> {
|
||||
//eventId,ClickedOn,Add
|
||||
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_QUOTE -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
|
||||
is SimpleAction.Edit -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(action.eventId))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_REPLY -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
||||
is SimpleAction.Quote -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(action.eventId))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_COPY_PERMALINK -> {
|
||||
val eventId = actionData.data.toString()
|
||||
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, eventId)
|
||||
is SimpleAction.Reply -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(action.eventId))
|
||||
}
|
||||
is SimpleAction.CopyPermalink -> {
|
||||
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)
|
||||
copyToClipboard(requireContext(), permalink, false)
|
||||
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||
|
||||
}
|
||||
MessageMenuViewModel.ACTION_RESEND -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.ResendMessage(eventId))
|
||||
is SimpleAction.Resend -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.ResendMessage(action.eventId))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_REMOVE -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(eventId))
|
||||
is SimpleAction.Remove -> {
|
||||
roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(action.eventId))
|
||||
}
|
||||
else -> {
|
||||
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
|
||||
else -> {
|
||||
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.ViewModel
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -25,15 +26,10 @@ import javax.inject.Inject
|
||||
*/
|
||||
class ActionsHandler @Inject constructor() : ViewModel() {
|
||||
|
||||
data class ActionData(
|
||||
val actionId: String,
|
||||
val data: Any?
|
||||
)
|
||||
val actionCommandEvent = MutableLiveData<LiveEvent<SimpleAction>>()
|
||||
|
||||
val actionCommandEvent = MutableLiveData<LiveEvent<ActionData>>()
|
||||
|
||||
fun fireAction(actionId: String, data: Any? = null) {
|
||||
actionCommandEvent.value = LiveEvent(ActionData(actionId,data))
|
||||
fun fireAction(action: SimpleAction) {
|
||||
actionCommandEvent.postLiveEvent(action)
|
||||
}
|
||||
|
||||
}
|
@ -89,7 +89,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
}
|
||||
menuActionFragment.interactionListener = object : MessageMenuFragment.InteractionListener {
|
||||
override fun didSelectMenuAction(simpleAction: SimpleAction) {
|
||||
actionHandlerModel.fireAction(simpleAction.uid, simpleAction.data)
|
||||
actionHandlerModel.fireAction(simpleAction)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
@ -105,7 +105,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.*
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
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
|
||||
|
||||
|
||||
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(
|
||||
val roomId: String,
|
||||
@ -68,24 +87,6 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
private val informationData: MessageInformationData = initialState.informationData
|
||||
|
||||
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? {
|
||||
val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.messageMenuViewModelFactory.create(state)
|
||||
@ -99,75 +100,64 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
private fun observeEvent() {
|
||||
RxRoom(room)
|
||||
.liveTimelineEvent(eventId)
|
||||
?.map {
|
||||
.map {
|
||||
actionsForEvent(it)
|
||||
}
|
||||
?.execute {
|
||||
.execute {
|
||||
copy(actions = it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun actionsForEvent(event: TimelineEvent): List<SimpleAction> {
|
||||
|
||||
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: event.root.getClearContent().toModel()
|
||||
val type = messageContent?.type
|
||||
|
||||
return if (event.root.sendState.hasFailed()) {
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
return arrayListOf<SimpleAction>().apply {
|
||||
if (event.root.sendState.hasFailed()) {
|
||||
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))
|
||||
}
|
||||
} else if (event.root.sendState.isSending()) {
|
||||
//TODO is uploading attachment?
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
add(SimpleAction.Remove(eventId))
|
||||
} else if (event.root.sendState.isSending()) {
|
||||
//TODO is uploading attachment?
|
||||
if (canCancel(event)) {
|
||||
this.add(SimpleAction(ACTION_CANCEL, R.string.cancel, R.drawable.ic_close_round, eventId))
|
||||
add(SimpleAction.Cancel(eventId))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
|
||||
} else {
|
||||
if (!event.root.isRedacted()) {
|
||||
|
||||
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)) {
|
||||
add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
|
||||
add(SimpleAction.Edit(eventId))
|
||||
}
|
||||
|
||||
if (canRedact(event, session.myUserId)) {
|
||||
add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
|
||||
add(SimpleAction.Delete(eventId))
|
||||
}
|
||||
|
||||
if (canCopy(type)) {
|
||||
//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()) {
|
||||
add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId))
|
||||
add(SimpleAction.AddReaction(eventId))
|
||||
}
|
||||
|
||||
if (canQuote(event, messageContent)) {
|
||||
add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId))
|
||||
add(SimpleAction.Quote(eventId))
|
||||
}
|
||||
|
||||
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 (messageContent is MessageImageContent) {
|
||||
add(
|
||||
SimpleAction(ACTION_SHARE,
|
||||
R.string.share, R.drawable.ic_share,
|
||||
session.contentUrlResolver().resolveFullSize(messageContent.url))
|
||||
)
|
||||
add(SimpleAction.Share(session.contentUrlResolver().resolveFullSize(messageContent.url)))
|
||||
}
|
||||
//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()) {
|
||||
val decryptedContent = event.root.toClearContentStringWithIndent()
|
||||
?: 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) {
|
||||
//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_EMOTE,
|
||||
MessageType.FORMAT_MATRIX_HTML,
|
||||
MessageType.MSGTYPE_LOCATION -> {
|
||||
true
|
||||
}
|
||||
MessageType.MSGTYPE_LOCATION -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -281,9 +269,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
return when (type) {
|
||||
MessageType.MSGTYPE_IMAGE,
|
||||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_VIDEO -> {
|
||||
true
|
||||
}
|
||||
MessageType.MSGTYPE_VIDEO -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -166,11 +166,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
root.isClickable = informationData.sendState.isSent()
|
||||
val state = if (informationData.hasPendingEdits) SendState.UNSENT else informationData.sendState
|
||||
textView?.setTextColor(colorProvider.getMessageTextColor(state))
|
||||
failureIndicator?.isVisible = when (informationData.sendState) {
|
||||
SendState.UNDELIVERED,
|
||||
SendState.FAILED_UNKNOWN_DEVICES -> true
|
||||
else -> false
|
||||
}
|
||||
failureIndicator?.isVisible = informationData.sendState.hasFailed()
|
||||
}
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
||||
|
@ -53,10 +53,6 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||
holder.mediaContentView.setOnLongClickListener(longClickListener)
|
||||
// The sending state color will be apply to the progress text
|
||||
renderSendState(holder.imageView, null, holder.failedToSendIndicator)
|
||||
holder.progressLayout
|
||||
if (informationData.sendState.hasFailed()) {
|
||||
|
||||
}
|
||||
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
|
||||
_openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId))
|
||||
_openRoomLiveData.postLiveEvent(action.roomSummary.roomId)
|
||||
}
|
||||
|
||||
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.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.glide.GlideRequest
|
||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import timber.log.Timber
|
||||
@ -67,27 +68,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||
imageView.layoutParams.height = height
|
||||
imageView.layoutParams.width = width
|
||||
|
||||
val glideRequest = 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)
|
||||
}
|
||||
|
||||
glideRequest
|
||||
createGlideRequest(data, mode, imageView, width, height)
|
||||
.dontAnimate()
|
||||
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
||||
.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 glideRequest = 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)
|
||||
}
|
||||
|
||||
glideRequest
|
||||
.listener(object: RequestListener<Drawable> {
|
||||
createGlideRequest(data, mode, imageView, width, height)
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
@ -140,7 +101,28 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||
})
|
||||
.fitCenter()
|
||||
.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) {
|
||||
|
@ -8,7 +8,6 @@
|
||||
<!-- enable window content transitions -->
|
||||
<item name="android:windowContentTransitions">true</item>
|
||||
|
||||
|
||||
<!-- specify shared element enter and exit transitions -->
|
||||
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
|
||||
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user