Merge branch 'develop' into feature/read_marker

This commit is contained in:
ganfra 2019-09-24 11:10:59 +02:00
commit 05d09bf950
99 changed files with 987 additions and 575 deletions

View file

@ -1,4 +1,25 @@
Changes in RiotX 0.6.0 (2019-XX-XX) Changes in RiotX 0.7.0 (2019-XX-XX)
===================================================
Features:
-
Improvements:
-
Other changes:
-
Bugfix:
-
Translations:
-
Build:
-
Changes in RiotX 0.6.0 (2019-09-24)
=================================================== ===================================================
Features: Features:
@ -10,14 +31,15 @@ Improvements:
- Remove any notification of a redacted event (#563) - Remove any notification of a redacted event (#563)
Other changes: Other changes:
- - Fix a few accessibility issues
Bugfix: Bugfix:
- Fix characters erased from the Search field when the result are coming (#545) - Fix characters erased from the Search field when the result are coming (#545)
- "No connection" banner was displayed by mistake - "No connection" banner was displayed by mistake
- Leaving community (from another client) has no effect on RiotX (#497)
Translations: - Push rules was not retrieved after a clear cache
- - m.notice messages trigger push notifications (#238)
- Embiggen messages with multiple emojis also for edited messages (#458)
Build: Build:
- Fix (again) issue with bad versionCode generated by Buildkite (#553) - Fix (again) issue with bad versionCode generated by Buildkite (#553)
@ -26,7 +48,7 @@ Changes in RiotX 0.5.0 (2019-09-17)
=================================================== ===================================================
Features: Features:
- Implementation of login to homeserver with SSO - Implementation of login to homeserver with SSO (#557)
- Handle M_CONSENT_NOT_GIVEN error (#64) - Handle M_CONSENT_NOT_GIVEN error (#64)
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link - Auto configure homeserver and identity server URLs of LoginActivity with a magic link

View file

@ -19,77 +19,92 @@ import im.vector.matrix.android.api.pushrules.rest.PushRule
import timber.log.Timber import timber.log.Timber
class Action(val type: Type) { sealed class Action {
object Notify : Action()
enum class Type(val value: String) { object DoNotNotify : Action()
NOTIFY("notify"), data class Sound(val sound: String) : Action()
DONT_NOTIFY("dont_notify"), data class Highlight(val highlight: Boolean) : Action()
COALESCE("coalesce"), }
SET_TWEAK("set_tweak");
companion object { private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
fun safeValueOf(value: String): Type? { private const val ACTION_COALESCE = "coalesce"
try {
return valueOf(value) // Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
} catch (e: IllegalArgumentException) { private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
return null
} private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
} private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
}
} private const val ACTION_OBJECT_VALUE_KEY = "value"
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
var tweak_action: String? = null
var stringValue: String? = null /**
var boolValue: Boolean? = null * Ref: https://matrix.org/docs/spec/client_server/latest#actions
*
companion object { * Convert
fun mapFrom(pushRule: PushRule): List<Action>? { * <pre>
val actions = ArrayList<Action>() * "actions": [
pushRule.actions.forEach { actionStrOrObj -> * "notify",
if (actionStrOrObj is String) { * {
when (actionStrOrObj) { * "set_tweak": "sound",
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY) * "value": "default"
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY) * },
else -> { * {
Timber.w("Unsupported action type ${actionStrOrObj}") * "set_tweak": "highlight"
null * }
} * ]
}?.let { *
actions.add(it) * To
} * [
} else if (actionStrOrObj is Map<*, *>) { * Action.Notify,
val tweakAction = actionStrOrObj["set_tweak"] as? String * Action.Sound("default"),
when (tweakAction) { * Action.Highlight(true)
"sound" -> { * ]
(actionStrOrObj["value"] as? String)?.let { stringValue -> *
Action(Action.Type.SET_TWEAK).also { * </pre>
it.tweak_action = "sound" */
it.stringValue = stringValue fun PushRule.getActions(): List<Action> {
actions.add(it) val result = ArrayList<Action>()
}
} actions.forEach { actionStrOrObj ->
} when (actionStrOrObj) {
"highlight" -> { ACTION_NOTIFY -> Action.Notify
(actionStrOrObj["value"] as? Boolean)?.let { boolValue -> ACTION_DONT_NOTIFY -> Action.DoNotNotify
Action(Action.Type.SET_TWEAK).also { is Map<*, *> -> {
it.tweak_action = "highlight" when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
it.boolValue = boolValue ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
actions.add(it) (actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
} Action.Sound(stringValue)
} }
} // When the value is not there, default sound (not specified by the spec)
else -> { ?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
Timber.w("Unsupported action type ${actionStrOrObj}")
} }
} ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
} else { (actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Timber.w("Unsupported action type ${actionStrOrObj}") Action.Highlight(boolValue)
return null }
} // When the value is not there, default is true, says the spec
} ?: Action.Highlight(true)
return if (actions.isEmpty()) null else actions }
} else -> {
} Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
null
}
}
}
else -> {
Timber.w("Unsupported action type $actionStrOrObj")
null
}
}?.let {
result.add(it)
}
}
return result
} }

View file

@ -25,14 +25,14 @@ interface PushRuleService {
/** /**
* Fetch the push rules from the server * Fetch the push rules from the server
*/ */
fun fetchPushRules(scope: String = "global") fun fetchPushRules(scope: String = RuleScope.GLOBAL)
//TODO get push rule set //TODO get push rule set
fun getPushRules(scope: String = "global"): List<PushRule> fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
//TODO update rule //TODO update rule
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun addPushRuleListener(listener: PushRuleListener) fun addPushRuleListener(listener: PushRuleListener)

View file

@ -37,7 +37,7 @@ object RuleIds {
// Default Underride Rules // Default Underride Rules
const val RULE_ID_CALL = ".m.rule.call" const val RULE_ID_CALL = ".m.rule.call"
const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one" const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one"
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one" const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message" const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted" const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"

View file

@ -15,12 +15,6 @@
*/ */
package im.vector.matrix.android.api.pushrules package im.vector.matrix.android.api.pushrules
object RuleScope {
enum class RulesetKey(val value: String) { const val GLOBAL = "global"
CONTENT("content"), }
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride"),
UNKNOWN("")
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.pushrules
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
*/
enum class RuleSetKey(val value: String) {
CONTENT("content"),
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride")
}
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules-scope-kind-ruleid
*/
typealias RuleKind = RuleSetKey

View file

@ -71,7 +71,7 @@ data class PushCondition(
this.key?.let { SenderNotificationPermissionCondition(it) } this.key?.let { SenderNotificationPermissionCondition(it) }
} }
Condition.Kind.UNRECOGNIZE -> { Condition.Kind.UNRECOGNIZE -> {
Timber.e("Unknwon kind $kind") Timber.e("Unknown kind $kind")
null null
} }
} }

View file

@ -16,12 +16,15 @@
package im.vector.matrix.android.api.session.group.model package im.vector.matrix.android.api.session.group.model
import im.vector.matrix.android.api.session.room.model.Membership
/** /**
* This class holds some data of a group. * This class holds some data of a group.
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService] * It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
*/ */
data class GroupSummary( data class GroupSummary(
val groupId: String, val groupId: String,
val membership: Membership,
val displayName: String = "", val displayName: String = "",
val shortDescription: String = "", val shortDescription: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",

View file

@ -16,9 +16,6 @@
package im.vector.matrix.android.api.session.pushers package im.vector.matrix.android.api.session.pushers
data class Pusher( data class Pusher(
val userId: String,
val pushKey: String, val pushKey: String,
val kind: String, val kind: String,
val appId: String, val appId: String,

View file

@ -20,7 +20,6 @@ import android.util.Patterns
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixPatterns.isUserId import im.vector.matrix.android.api.MatrixPatterns.isUserId
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
@ -219,7 +218,7 @@ class CreateRoomParams {
* @param ids the participant ids to add. * @param ids the participant ids to add.
*/ */
fun addParticipantIds(hsConfig: HomeServerConnectionConfig, fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
credentials: Credentials, userId: String,
ids: List<String>) { ids: List<String>) {
for (id in ids) { for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) { if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
@ -233,7 +232,7 @@ class CreateRoomParams {
invite3pids!!.add(pid) invite3pids!!.add(pid)
} else if (isUserId(id)) { } else if (isUserId(id)) {
// do not invite oneself // do not invite oneself
if (credentials.userId != id) { if (userId != id) {
if (null == invitedUserIds) { if (null == invitedUserIds) {
invitedUserIds = ArrayList() invitedUserIds = ArrayList()
} }

View file

@ -16,14 +16,14 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore, internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
private val credentials: Credentials, @UserId private val userId: String,
private val keysBackup: KeysBackup) { private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) { fun handle(verificationStatus: Int, deviceId: String, userId: String) {
@ -39,7 +39,7 @@ internal class SetDeviceVerificationAction @Inject constructor(private val crypt
device.verified = verificationStatus device.verified = verificationStatus
cryptoStore.storeUserDevice(userId, device) cryptoStore.storeUserDevice(userId, device)
if (userId == credentials.userId) { if (userId == this.userId) {
// If one of the user's own devices is being marked as verified / unverified, // If one of the user's own devices is being marked as verified / unverified,
// check the key backup status, since whether or not we use this depends on // check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device // whether it has a signature from a verified device

View file

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
@ -40,7 +39,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
internal class MXMegolmDecryption(private val credentials: Credentials, internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
@ -146,11 +145,11 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val selfMap = HashMap<String, String>() val selfMap = HashMap<String, String>()
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager) // TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
selfMap["userId"] = credentials.userId selfMap["userId"] = userId
selfMap["deviceId"] = "*" selfMap["deviceId"] = "*"
recipients.add(selfMap) recipients.add(selfMap)
if (!TextUtils.equals(sender, credentials.userId)) { if (!TextUtils.equals(sender, userId)) {
val senderMap = HashMap<String, String>() val senderMap = HashMap<String, String>()
senderMap["userId"] = sender senderMap["userId"] = sender
senderMap["deviceId"] = encryptedEventContent.deviceId!! senderMap["deviceId"] = encryptedEventContent.deviceId!!

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
@ -24,10 +23,11 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject import javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(private val credentials: Credentials, internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
@ -39,7 +39,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(private val credent
fun create(): MXMegolmDecryption { fun create(): MXMegolmDecryption {
return MXMegolmDecryption( return MXMegolmDecryption(
credentials, userId,
olmDevice, olmDevice,
deviceListManager, deviceListManager,
outgoingRoomKeyRequestManager, outgoingRoomKeyRequestManager,

View file

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -35,8 +34,8 @@ import timber.log.Timber
internal class MXOlmDecryption( internal class MXOlmDecryption(
// The olm device interface // The olm device interface
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
// the matrix credentials // the matrix userId
private val credentials: Credentials) private val userId: String)
: IMXDecrypting { : IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
@ -97,9 +96,9 @@ internal class MXOlmDecryption(
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
} }
if (olmPayloadContent.recipient != credentials.userId) { if (olmPayloadContent.recipient != userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" + Timber.e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") " Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)) String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
} }

View file

@ -16,16 +16,16 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.di.UserId
import javax.inject.Inject import javax.inject.Inject
internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,
private val credentials: Credentials) { @UserId private val userId: String) {
fun create(): MXOlmDecryption { fun create(): MXOlmDecryption {
return MXOlmDecryption( return MXOlmDecryption(
olmDevice, olmDevice,
credentials) userId)
} }
} }

View file

@ -16,11 +16,11 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +34,7 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
} }
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi, internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi,
private val credentials: Credentials) @UserId private val userId: String)
: DeleteDeviceWithUserPasswordTask { : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) { override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
@ -45,7 +45,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(priva
.apply { .apply {
type = LoginFlowTypes.PASSWORD type = LoginFlowTypes.PASSWORD
session = params.authSession session = params.authSession
user = credentials.userId user = userId
password = params.password password = params.password
} }
}) })

View file

@ -22,14 +22,15 @@ import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
internal object GroupSummaryMapper { internal object GroupSummaryMapper {
fun map(roomSummaryEntity: GroupSummaryEntity): GroupSummary { fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
return GroupSummary( return GroupSummary(
roomSummaryEntity.groupId, groupSummaryEntity.groupId,
roomSummaryEntity.displayName, groupSummaryEntity.membership,
roomSummaryEntity.shortDescription, groupSummaryEntity.displayName,
roomSummaryEntity.avatarUrl, groupSummaryEntity.shortDescription,
roomSummaryEntity.roomIds.toList(), groupSummaryEntity.avatarUrl,
roomSummaryEntity.userIds.toList() groupSummaryEntity.roomIds.toList(),
groupSummaryEntity.userIds.toList()
) )
} }
} }

View file

@ -26,7 +26,6 @@ internal object PushersMapper {
fun map(pushEntity: PusherEntity): Pusher { fun map(pushEntity: PusherEntity): Pusher {
return Pusher( return Pusher(
userId = pushEntity.userId,
pushKey = pushEntity.pushKey, pushKey = pushEntity.pushKey,
kind = pushEntity.kind ?: "", kind = pushEntity.kind ?: "",
appId = pushEntity.appId, appId = pushEntity.appId,
@ -39,9 +38,8 @@ internal object PushersMapper {
) )
} }
fun map(pusher: JsonPusher, userId: String): PusherEntity { fun map(pusher: JsonPusher): PusherEntity {
return PusherEntity( return PusherEntity(
userId = userId,
pushKey = pusher.pushKey, pushKey = pusher.pushKey,
kind = pusher.kind, kind = pusher.kind,
appId = pusher.appId, appId = pusher.appId,
@ -58,6 +56,6 @@ internal fun PusherEntity.asDomain(): Pusher {
return PushersMapper.map(this) return PushersMapper.map(this)
} }
internal fun JsonPusher.toEntity(userId: String): PusherEntity { internal fun JsonPusher.toEntity(): PusherEntity {
return PushersMapper.map(this, userId) return PushersMapper.map(this)
} }

View file

@ -20,9 +20,13 @@ import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class GroupEntity(@PrimaryKey var groupId: String = "" /**
* This class is used to store group info (groupId and membership) from the sync response.
) : RealmObject() { * Then [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] observes change and
* makes requests to fetch group information from the homeserver
*/
internal open class GroupEntity(@PrimaryKey var groupId: String = "")
: RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name
var membership: Membership var membership: Membership

View file

@ -16,18 +16,28 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "", internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
var displayName: String = "", var displayName: String = "",
var shortDescription: String = "", var shortDescription: String = "",
var avatarUrl: String = "", var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList(), var roomIds: RealmList<String> = RealmList(),
var userIds: RealmList<String> = RealmList() var userIds: RealmList<String> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
var membership: Membership
get() {
return Membership.valueOf(membershipStr)
}
set(value) {
membershipStr = value.name
}
companion object companion object
} }

View file

@ -15,17 +15,24 @@
*/ */
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.pushrules.RuleKind
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Index
internal open class PushRulesEntity( internal open class PushRulesEntity(
@Index var userId: String = "",
var scope: String = "", var scope: String = "",
// "content", etc.
var rulesetKey: String = "",
var pushRules: RealmList<PushRuleEntity> = RealmList() var pushRules: RealmList<PushRuleEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var kindStr: String = RuleKind.CONTENT.name
var kind: RuleKind
get() {
return RuleKind.valueOf(kindStr)
}
set(value) {
kindStr = value.name
}
companion object companion object
} }

View file

@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.pushers.PusherState import im.vector.matrix.android.api.session.pushers.PusherState
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Index
//TODO //TODO
// at java.lang.Thread.run(Thread.java:764) // at java.lang.Thread.run(Thread.java:764)
@ -29,7 +28,6 @@ import io.realm.annotations.Index
// at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70) // at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70)
// at io.realm.Realm.executeTransaction(Realm.java:1493) // at io.realm.Realm.executeTransaction(Realm.java:1493)
internal open class PusherEntity( internal open class PusherEntity(
@Index var userId: String = "",
var pushKey: String = "", var pushKey: String = "",
var kind: String? = null, var kind: String? = null,
var appId: String = "", var appId: String = "",

View file

@ -23,9 +23,9 @@ import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
internal fun GroupEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<GroupEntity> { internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQuery<GroupEntity> {
return realm.where<GroupEntity>() return realm.where<GroupEntity>()
.equalTo(GroupEntityFields.GROUP_ID, roomId) .equalTo(GroupEntityFields.GROUP_ID, groupId)
} }
internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> { internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> {

View file

@ -30,3 +30,7 @@ internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? =
return query return query
} }
internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List<String>): RealmQuery<GroupSummaryEntity> {
return realm.where<GroupSummaryEntity>()
.`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray())
}

View file

@ -15,6 +15,7 @@
*/ */
package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.internal.database.model.PushRulesEntity import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntityFields import im.vector.matrix.android.internal.database.model.PushRulesEntityFields
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
@ -24,10 +25,8 @@ import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
internal fun PusherEntity.Companion.where(realm: Realm, internal fun PusherEntity.Companion.where(realm: Realm,
userId: String,
pushKey: String? = null): RealmQuery<PusherEntity> { pushKey: String? = null): RealmQuery<PusherEntity> {
return realm.where<PusherEntity>() return realm.where<PusherEntity>()
.equalTo(PusherEntityFields.USER_ID, userId)
.apply { .apply {
if (pushKey != null) { if (pushKey != null) {
equalTo(PusherEntityFields.PUSH_KEY, pushKey) equalTo(PusherEntityFields.PUSH_KEY, pushKey)
@ -36,11 +35,9 @@ internal fun PusherEntity.Companion.where(realm: Realm,
} }
internal fun PushRulesEntity.Companion.where(realm: Realm, internal fun PushRulesEntity.Companion.where(realm: Realm,
userId: String,
scope: String, scope: String,
ruleSetKey: String): RealmQuery<PushRulesEntity> { kind: RuleKind): RealmQuery<PushRulesEntity> {
return realm.where<PushRulesEntity>() return realm.where<PushRulesEntity>()
.equalTo(PushRulesEntityFields.USER_ID, userId)
.equalTo(PushRulesEntityFields.SCOPE, scope) .equalTo(PushRulesEntityFields.SCOPE, scope)
.equalTo(PushRulesEntityFields.RULESET_KEY, ruleSetKey) .equalTo(PushRulesEntityFields.KIND_STR, kind.name)
} }

View file

@ -30,12 +30,18 @@ internal fun isEventRead(monarchy: Monarchy,
var isEventRead = false var isEventRead = false
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() ?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex ?: Int.MIN_VALUE val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex ?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex isEventRead = if (eventToCheck?.sender == userId) {
true
} else {
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex ?: Int.MIN_VALUE
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
eventToCheckIndex <= readReceiptIndex
}
} }
return isEventRead return isEventRead

View file

@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules
object MoshiProvider { object MoshiProvider {
@ -31,6 +32,7 @@ object MoshiProvider {
.add(UriMoshiAdapter()) .add(UriMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
) )
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)

View file

@ -18,6 +18,16 @@ package im.vector.matrix.android.internal.di
import javax.inject.Qualifier import javax.inject.Qualifier
/**
* Used to inject the userId
*/
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class UserMd5 internal annotation class UserId
/**
* Used to inject the md5 of the userId
*/
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class UserMd5

View file

@ -20,6 +20,7 @@ import android.content.Context
import com.novoda.merlin.Merlin import com.novoda.merlin.Merlin
import com.novoda.merlin.MerlinsBeard import com.novoda.merlin.MerlinsBeard
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +28,9 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@MatrixScope @MatrixScope
internal class NetworkConnectivityChecker @Inject constructor(context: Context) { internal class NetworkConnectivityChecker @Inject constructor(context: Context,
backgroundDetectionObserver: BackgroundDetectionObserver)
: BackgroundDetectionObserver.Listener {
private val merlin = Merlin.Builder() private val merlin = Merlin.Builder()
.withConnectableCallbacks() .withConnectableCallbacks()
@ -41,7 +44,12 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context)
private set private set
init { init {
backgroundDetectionObserver.register(this)
}
override fun onMoveToForeground() {
merlin.bind() merlin.bind()
merlin.registerDisconnectable { merlin.registerDisconnectable {
if (hasInternetAccess) { if (hasInternetAccess) {
Timber.v("On Disconnect") Timber.v("On Disconnect")
@ -64,6 +72,10 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context)
} }
} }
override fun onMoveToBackground() {
merlin.unbind()
}
suspend fun waitUntilConnected() { suspend fun waitUntilConnected() {
if (hasInternetAccess) { if (hasInternetAccess) {
return return

View file

@ -20,11 +20,11 @@ import android.content.Context
import android.os.Environment import android.os.Environment
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
@ -40,7 +40,7 @@ import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
internal class DefaultFileService @Inject constructor(private val context: Context, internal class DefaultFileService @Inject constructor(private val context: Context,
private val sessionParams: SessionParams, @UserMd5 private val userMd5: String,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
@ -105,7 +105,7 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
// Create dir tree (MF stands for Matrix File): // Create dir tree (MF stands for Matrix File):
// <cache>/MF/<md5(userId)>/<md5(id)>/ // <cache>/MF/<md5(userId)>/<md5(id)>/
val tmpFolderRoot = File(context.cacheDir, "MF") val tmpFolderRoot = File(context.cacheDir, "MF")
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5()) val tmpFolderUser = File(tmpFolderRoot, userMd5)
File(tmpFolderUser, id.md5()) File(tmpFolderUser, id.md5())
} }
FileService.DownloadMode.TO_EXPORT -> { FileService.DownloadMode.TO_EXPORT -> {

View file

@ -67,11 +67,18 @@ internal abstract class SessionModule {
return sessionParams.credentials return sessionParams.credentials
} }
@JvmStatic
@UserId
@Provides
fun providesUserId(credentials: Credentials): String {
return credentials.userId
}
@JvmStatic @JvmStatic
@UserMd5 @UserMd5
@Provides @Provides
fun providesUserMd5(sessionParams: SessionParams): String { fun providesUserMd5(@UserId userId: String): String {
return sessionParams.credentials.userId.md5() return userId.md5()
} }
@JvmStatic @JvmStatic

View file

@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.session.filter package im.vector.matrix.android.internal.session.filter
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,7 +33,7 @@ internal interface SaveFilterTask : Task<SaveFilterTask.Params, Unit> {
} }
internal class DefaultSaveFilterTask @Inject constructor(private val sessionParams: SessionParams, internal class DefaultSaveFilterTask @Inject constructor(@UserId private val userId: String,
private val filterAPI: FilterApi, private val filterAPI: FilterApi,
private val filterRepository: FilterRepository private val filterRepository: FilterRepository
) : SaveFilterTask { ) : SaveFilterTask {
@ -41,7 +41,7 @@ internal class DefaultSaveFilterTask @Inject constructor(private val sessionPara
override suspend fun execute(params: SaveFilterTask.Params) { override suspend fun execute(params: SaveFilterTask.Params) {
val filterResponse = executeRequest<FilterResponse> { val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry // TODO auto retry
apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter) apiCall = filterAPI.uploadFilter(userId, params.filter)
} }
filterRepository.storeFilterId(params.filter, filterResponse.filterId) filterRepository.storeFilterId(params.filter, filterResponse.filterId)
} }

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -64,8 +65,7 @@ internal class DefaultGetGroupDataTask @Inject constructor(
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: "" groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name val name = groupSummary.profile?.name
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""
?: ""
val roomIds = groupRooms.rooms.map { it.roomId } val roomIds = groupRooms.rooms.map { it.roomId }
groupSummaryEntity.roomIds.clear() groupSummaryEntity.roomIds.clear()
@ -74,8 +74,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
val userIds = groupUsers.users.map { it.userId } val userIds = groupUsers.users.map { it.userId }
groupSummaryEntity.userIds.clear() groupSummaryEntity.userIds.clear()
groupSummaryEntity.userIds.addAll(userIds) groupSummaryEntity.userIds.addAll(userIds)
groupSummaryEntity.membership = when (groupSummary.user?.membership) {
Membership.JOIN.value -> Membership.JOIN
Membership.INVITE.value -> Membership.INVITE
else -> Membership.LEAVE
}
} }
} }
} }

View file

@ -20,35 +20,48 @@ import android.content.Context
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.UserId
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 io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults import io.realm.RealmResults
import javax.inject.Inject import javax.inject.Inject
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class GroupSummaryUpdater @Inject constructor(private val context: Context, internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
private val credentials: Credentials, @UserId private val userId: String,
@SessionDatabase realmConfiguration: RealmConfiguration) private val monarchy: Monarchy)
: RealmLiveEntityObserver<GroupEntity>(realmConfiguration) { : RealmLiveEntityObserver<GroupEntity>(monarchy.realmConfiguration) {
override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) } override val query = Monarchy.Query { GroupEntity.where(it) }
override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) { override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) {
val newGroupIds = changeSet.insertions // `insertions` for new groups and `changes` to handle left groups
val modifiedGroupEntity = (changeSet.insertions + changeSet.changes)
.asSequence() .asSequence()
.mapNotNull { results[it]?.groupId} .mapNotNull { results[it] }
.toList()
fetchGroupsData(modifiedGroupEntity
.filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
.map { it.groupId }
.toList())
deleteGroups(modifiedGroupEntity
.filter { it.membership == Membership.LEAVE }
.map { it.groupId }
.toList())
}
private fun fetchGroupsData(groupIds: List<String>) {
val getGroupDataWorkerParams = GetGroupDataWorker.Params(userId, groupIds)
val getGroupDataWorkerParams = GetGroupDataWorker.Params(credentials.userId, newGroupIds)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>() val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
@ -61,4 +74,15 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
.enqueue() .enqueue()
} }
/**
* Delete the GroupSummaryEntity of left groups
*/
private fun deleteGroups(groupIds: List<String>) {
monarchy
.writeAsync { realm ->
GroupSummaryEntity.where(realm, groupIds)
.findAll()
.deleteAllFromRealm()
}
}
} }

View file

@ -17,9 +17,10 @@ package im.vector.matrix.android.internal.session.notification
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.RuleSetKey
import im.vector.matrix.android.api.pushrules.getActions
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -35,53 +36,60 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
internal class DefaultPushRuleService @Inject constructor( internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
private val sessionParams: SessionParams, private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val pushRulesTask: GetPushRulesTask, private val taskExecutor: TaskExecutor,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask, private val monarchy: Monarchy
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService { ) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>() private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun fetchPushRules(scope: String) { override fun fetchPushRules(scope: String) {
pushRulesTask getPushRulesTask
.configureWith(GetPushRulesTask.Params(scope)) .configureWith(GetPushRulesTask.Params(scope))
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun getPushRules(scope: String): List<PushRule> { override fun getPushRules(scope: String): List<PushRule> {
var contentRules: List<PushRule> = emptyList() var contentRules: List<PushRule> = emptyList()
var overrideRules: List<PushRule> = emptyList() var overrideRules: List<PushRule> = emptyList()
var roomRules: List<PushRule> = emptyList() var roomRules: List<PushRule> = emptyList()
var senderRules: List<PushRule> = emptyList() var senderRules: List<PushRule> = emptyList()
var underrideRules: List<PushRule> = emptyList() var underrideRules: List<PushRule> = emptyList()
// TODO Create const for ruleSetKey
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re -> PushRulesEntity.where(realm, scope, RuleSetKey.CONTENT)
contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) } .findFirst()
} ?.let { pushRulesEntity ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re -> contentRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapContentRule(it) }
overrideRules = re.pushRules.map { PushRulesMapper.map(it) } }
} PushRulesEntity.where(realm, scope, RuleSetKey.OVERRIDE)
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re -> .findFirst()
roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) } ?.let { pushRulesEntity ->
} overrideRules = pushRulesEntity.pushRules.map { PushRulesMapper.map(it) }
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re -> }
senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) } PushRulesEntity.where(realm, scope, RuleSetKey.ROOM)
} .findFirst()
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re -> ?.let { pushRulesEntity ->
underrideRules = re.pushRules.map { PushRulesMapper.map(it) } roomRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapRoomRule(it) }
} }
PushRulesEntity.where(realm, scope, RuleSetKey.SENDER)
.findFirst()
?.let { pushRulesEntity ->
senderRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapSenderRule(it) }
}
PushRulesEntity.where(realm, scope, RuleSetKey.UNDERRIDE)
.findFirst()
?.let { pushRulesEntity ->
underrideRules = pushRulesEntity.pushRules.map { PushRulesMapper.map(it) }
}
} }
return contentRules + overrideRules + roomRules + senderRules + underrideRules // Ref. for the order: https://matrix.org/docs/spec/client_server/latest#push-rules
return overrideRules + contentRules + roomRules + senderRules + underrideRules
} }
override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable { override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
return updatePushRuleEnableStatusTask return updatePushRuleEnableStatusTask
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) { .configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) {
this.callback = callback this.callback = callback
@ -114,8 +122,9 @@ internal class DefaultPushRuleService @Inject constructor(
fun dispatchBing(event: Event, rule: PushRule) { fun dispatchBing(event: Event, rule: PushRule) {
try { try {
val actionsList = rule.getActions()
listeners.forEach { listeners.forEach {
it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList()) it.onMatchRule(event, actionsList)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "Error while dispatching bing") Timber.e(e, "Error while dispatching bing")

View file

@ -16,11 +16,11 @@
package im.vector.matrix.android.internal.session.notification package im.vector.matrix.android.internal.session.notification
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -37,7 +37,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
internal class DefaultProcessEventForPushTask @Inject constructor( internal class DefaultProcessEventForPushTask @Inject constructor(
private val defaultPushRuleService: DefaultPushRuleService, private val defaultPushRuleService: DefaultPushRuleService,
private val roomService: RoomService, private val roomService: RoomService,
private val sessionParams: SessionParams @UserId private val userId: String
) : ProcessEventForPushTask { ) : ProcessEventForPushTask {
override suspend fun execute(params: ProcessEventForPushTask.Params) { override suspend fun execute(params: ProcessEventForPushTask.Params) {
@ -68,7 +68,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
else -> false else -> false
} }
}.filter { }.filter {
it.senderId != sessionParams.credentials.userId it.senderId != userId
} }
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules") " to check for push rules with ${params.rules.size} rules")
@ -101,7 +101,8 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
} }
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? { private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
val conditionResolver = DefaultConditionResolver(event, roomService, sessionParams) // TODO This should be injected
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
rules.filter { it.enabled }.forEach { rule -> rules.filter { it.enabled }.forEach { rule ->
val isFullfilled = rule.conditions?.map { val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false

View file

@ -57,14 +57,14 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
return Result.failure() return Result.failure()
} }
return try { return try {
setPusher(pusher, params.userId) setPusher(pusher)
Result.success() Result.success()
} catch (exception: Throwable) { } catch (exception: Throwable) {
when (exception) { when (exception) {
is Failure.NetworkConnection -> Result.retry() is Failure.NetworkConnection -> Result.retry()
else -> { else -> {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()?.let { PusherEntity.where(realm, pusher.pushKey).findFirst()?.let {
//update it //update it
it.state = PusherState.FAILED_TO_REGISTER it.state = PusherState.FAILED_TO_REGISTER
} }
@ -76,12 +76,12 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
} }
} }
private suspend fun setPusher(pusher: JsonPusher, userId: String) { private suspend fun setPusher(pusher: JsonPusher) {
executeRequest<Unit> { executeRequest<Unit> {
apiCall = pushersAPI.setPusher(pusher) apiCall = pushersAPI.setPusher(pusher)
} }
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val echo = PusherEntity.where(realm, userId, pusher.pushKey).findFirst() val echo = PusherEntity.where(realm, pusher.pushKey).findFirst()
if (echo != null) { if (echo != null) {
//update it //update it
echo.appDisplayName = pusher.appDisplayName echo.appDisplayName = pusher.appDisplayName
@ -93,7 +93,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
echo.data?.url = pusher.data?.url echo.data?.url = pusher.data?.url
echo.state = PusherState.REGISTERED echo.state = PusherState.REGISTERED
} else { } else {
pusher.toEntity(userId).also { pusher.toEntity().also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
} }

View file

@ -15,15 +15,16 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.* import im.vector.matrix.android.api.pushrules.*
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber import timber.log.Timber
// TODO Inject constructor
internal class DefaultConditionResolver(private val event: Event, internal class DefaultConditionResolver(private val event: Event,
private val roomService: RoomService, private val roomService: RoomService,
private val sessionParams: SessionParams) : ConditionResolver { @UserId private val userId: String) : ConditionResolver {
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean { override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
@ -45,8 +46,7 @@ internal class DefaultConditionResolver(private val event: Event,
override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean { override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean {
val roomId = event.roomId ?: return false val roomId = event.roomId ?: return false
val room = roomService.getRoom(roomId) ?: return false val room = roomService.getRoom(roomId) ?: return false
val myDisplayName = room.getRoomMember(sessionParams.credentials.userId)?.displayName val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
?: return false
return containsDisplayNameCondition.isSatisfied(event, myDisplayName) return containsDisplayNameCondition.isSatisfied(event, myDisplayName)
} }
} }

View file

@ -21,29 +21,28 @@ import androidx.work.BackoffPolicy
import androidx.work.WorkManager import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.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 java.util.UUID import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal class DefaultPusherService @Inject constructor( internal class DefaultPusherService @Inject constructor(private val context: Context,
private val context: Context, private val monarchy: Monarchy,
private val monarchy: Monarchy, @UserId private val userId: String,
private val sessionParam: SessionParams, private val getPusherTask: GetPushersTask,
private val getPusherTask: GetPushersTask, private val removePusherTask: RemovePusherTask,
private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor
private val taskExecutor: TaskExecutor
) : PushersService { ) : PushersService {
@ -70,7 +69,7 @@ internal class DefaultPusherService @Inject constructor(
append = append) append = append)
val params = AddHttpPusherWorker.Params(pusher, sessionParam.credentials.userId) val params = AddHttpPusherWorker.Params(pusher, userId)
val request = matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>() val request = matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerUtil.workConstraints)
@ -82,7 +81,7 @@ internal class DefaultPusherService @Inject constructor(
} }
override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) { override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) {
val params = RemovePusherTask.Params(sessionParam.credentials.userId, pushkey, appId) val params = RemovePusherTask.Params(pushkey, appId)
removePusherTask removePusherTask
.configureWith(params) { .configureWith(params) {
this.callback = callback this.callback = callback
@ -93,12 +92,12 @@ internal class DefaultPusherService @Inject constructor(
override fun livePushers(): LiveData<List<Pusher>> { override fun livePushers(): LiveData<List<Pusher>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ realm -> PusherEntity.where(realm, sessionParam.credentials.userId) }, { realm -> PusherEntity.where(realm) },
{ it.asDomain() } { it.asDomain() }
) )
} }
override fun pushers(): List<Pusher> { override fun pushers(): List<Pusher> {
return monarchy.fetchAllCopiedSync { PusherEntity.where(it, sessionParam.credentials.userId) }.map { it.asDomain() } return monarchy.fetchAllCopiedSync { PusherEntity.where(it) }.map { it.asDomain() }
} }
} }

View file

@ -15,80 +15,27 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject import javax.inject.Inject
internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> { internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> {
data class Params(val scope: String) data class Params(val scope: String)
} }
/**
* We keep this task, but it should not be used anymore, the push rules comes from the sync response
*/
internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi, internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi,
private val monarchy: Monarchy, private val savePushRulesTask: SavePushRulesTask) : GetPushRulesTask {
private val sessionParams: SessionParams) : GetPushRulesTask {
override suspend fun execute(params: GetPushRulesTask.Params) { override suspend fun execute(params: GetPushRulesTask.Params) {
val response = executeRequest<GetPushRulesResponse> { val response = executeRequest<GetPushRulesResponse> {
apiCall = pushRulesApi.getAllRules() apiCall = pushRulesApi.getAllRules()
} }
val scope = params.scope
monarchy.awaitTransaction { realm ->
//clear existings?
//TODO
realm.where(PushRulesEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm()
val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") savePushRulesTask.execute(SavePushRulesTask.Params(response))
response.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also {
content.pushRules.add(it)
}
}
realm.insertOrUpdate(content)
val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
response.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
response.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)
val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
response.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)
val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
response.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
}
} }
} }

View file

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.pushers.PusherState import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction import im.vector.matrix.android.internal.util.awaitTransaction
@ -29,8 +27,7 @@ import javax.inject.Inject
internal interface GetPushersTask : Task<Unit, Unit> internal interface GetPushersTask : Task<Unit, Unit>
internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI, internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy) : GetPushersTask {
private val sessionParams: SessionParams) : GetPushersTask {
override suspend fun execute(params: Unit) { override suspend fun execute(params: Unit) {
val response = executeRequest<GetPushersResponse> { val response = executeRequest<GetPushersResponse> {
@ -39,10 +36,9 @@ internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI:
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
//clear existings? //clear existings?
realm.where(PusherEntity::class.java) realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm() .findAll().deleteAllFromRealm()
response.pushers?.forEach { jsonPusher -> response.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParams.credentials.userId).also { jsonPusher.toEntity().also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
} }

View file

@ -59,6 +59,9 @@ internal abstract class PushersModule {
@Binds @Binds
abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask
@Binds
abstract fun bindSavePushRulesTask(savePushRulesTask: DefaultSavePushRulesTask): SavePushRulesTask
@Binds @Binds
abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask

View file

@ -28,8 +28,7 @@ import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> { internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
data class Params(val userId: String, data class Params(val pushKey: String,
val pushKey: String,
val pushAppId: String) val pushAppId: String)
} }
@ -40,12 +39,12 @@ internal class DefaultRemovePusherTask @Inject constructor(
override suspend fun execute(params: RemovePusherTask.Params) { override suspend fun execute(params: RemovePusherTask.Params) {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val existingEntity = PusherEntity.where(realm, params.userId, params.pushKey).findFirst() val existingEntity = PusherEntity.where(realm, params.pushKey).findFirst()
existingEntity?.state = PusherState.UNREGISTERING existingEntity?.state = PusherState.UNREGISTERING
} }
val existing = Realm.getInstance(monarchy.realmConfiguration).use { realm -> val existing = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
PusherEntity.where(realm, params.userId, params.pushKey).findFirst()?.asDomain() PusherEntity.where(realm, params.pushKey).findFirst()?.asDomain()
} ?: throw Exception("No existing pusher") } ?: throw Exception("No existing pusher")
val deleteBody = JsonPusher( val deleteBody = JsonPusher(
@ -64,7 +63,7 @@ internal class DefaultRemovePusherTask @Inject constructor(
apiCall = pushersAPI.setPusher(deleteBody) apiCall = pushersAPI.setPusher(deleteBody)
} }
monarchy.awaitTransaction { monarchy.awaitTransaction {
PusherEntity.where(it, params.userId, params.pushKey).findFirst()?.deleteFromRealm() PusherEntity.where(it, params.pushKey).findFirst()?.deleteFromRealm()
} }
} }
} }

View file

@ -0,0 +1,81 @@
/*
* 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.session.pushers
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.pushrules.RuleSetKey
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject
/**
* Save the push rules in DB
*/
internal interface SavePushRulesTask : Task<SavePushRulesTask.Params, Unit> {
data class Params(val pushRules: GetPushRulesResponse)
}
internal class DefaultSavePushRulesTask @Inject constructor(private val monarchy: Monarchy) : SavePushRulesTask {
override suspend fun execute(params: SavePushRulesTask.Params) {
monarchy.awaitTransaction { realm ->
// clear current push rules
realm.where(PushRulesEntity::class.java)
.findAll()
.deleteAllFromRealm()
// Save only global rules for the moment
val globalRules = params.pushRules.global
val content = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.CONTENT }
globalRules.content?.forEach { rule ->
content.pushRules.add(PushRulesMapper.map(rule))
}
realm.insertOrUpdate(content)
val override = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.OVERRIDE }
globalRules.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
val rooms = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.ROOM }
globalRules.room?.forEach { rule ->
rooms.pushRules.add(PushRulesMapper.map(rule))
}
realm.insertOrUpdate(rooms)
val senders = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.SENDER }
globalRules.sender?.forEach { rule ->
senders.pushRules.add(PushRulesMapper.map(rule))
}
realm.insertOrUpdate(senders)
val underrides = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.UNDERRIDE }
globalRules.underride?.forEach { rule ->
underrides.pushRules.add(PushRulesMapper.map(rule))
}
realm.insertOrUpdate(underrides)
}
}
}

View file

@ -15,6 +15,7 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -22,7 +23,7 @@ import javax.inject.Inject
internal interface UpdatePushRuleEnableStatusTask : Task<UpdatePushRuleEnableStatusTask.Params, Unit> { internal interface UpdatePushRuleEnableStatusTask : Task<UpdatePushRuleEnableStatusTask.Params, Unit> {
data class Params(val kind: String, data class Params(val kind: RuleKind,
val pushRule: PushRule, val pushRule: PushRule,
val enabled: Boolean) val enabled: Boolean)
} }
@ -32,7 +33,7 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(private
override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) { override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) {
return executeRequest { return executeRequest {
apiCall = pushRulesApi.updateEnableRuleStatus(params.kind, params.pushRule.ruleId, params.enabled) apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled)
} }
} }
} }

View file

@ -16,13 +16,13 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
@ -38,7 +38,7 @@ import javax.inject.Inject
*/ */
internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
private val credentials: Credentials, @UserId private val userId: String,
private val task: EventRelationsAggregationTask, private val task: EventRelationsAggregationTask,
private val taskExecutor: TaskExecutor) : private val taskExecutor: TaskExecutor) :
RealmLiveEntityObserver<EventEntity>(realmConfiguration) { RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
@ -61,7 +61,7 @@ internal class EventRelationsAggregationUpdater @Inject constructor(@SessionData
.toList() .toList()
val params = EventRelationsAggregationTask.Params( val params = EventRelationsAggregationTask.Params(
insertedDomains, insertedDomains,
credentials.userId userId
) )
task.configureWith(params).executeBy(taskExecutor) task.configureWith(params).executeBy(taskExecutor)
} }

View file

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
@ -27,11 +26,12 @@ import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import javax.inject.Inject import javax.inject.Inject
internal class RoomAvatarResolver @Inject constructor(private val monarchy: Monarchy, internal class RoomAvatarResolver @Inject constructor(private val monarchy: Monarchy,
private val credentials: Credentials) { @UserId private val userId: String) {
/** /**
* Compute the room avatar url * Compute the room avatar url
@ -52,7 +52,7 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
if (members.size == 1) { if (members.size == 1) {
res = members.firstOrNull()?.toRoomMember()?.avatarUrl res = members.firstOrNull()?.toRoomMember()?.avatarUrl
} else if (members.size == 2) { } else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, credentials.userId).findFirst() val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, userId).findFirst()
res = firstOtherMember?.toRoomMember()?.avatarUrl res = firstOtherMember?.toRoomMember()?.avatarUrl
} }
} }

View file

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
@ -31,6 +30,7 @@ import im.vector.matrix.android.internal.database.query.isEventRead
import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
@ -39,7 +39,7 @@ import io.realm.Realm
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryUpdater @Inject constructor(private val credentials: Credentials, internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver, private val roomAvatarResolver: RoomAvatarResolver,
private val monarchy: Monarchy) { private val monarchy: Monarchy) {
@ -92,11 +92,11 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
//avoid this call if we are sure there are unread events //avoid this call if we are sure there are unread events
|| !isEventRead(monarchy, credentials.userId, roomId, latestPreviewableEvent?.eventId) || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
val otherRoomMembers = RoomMembers(realm, roomId) val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent() .queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId) .notEqualTo(EventEntityFields.STATE_KEY, userId)
.findAll() .findAll()
.asSequence() .asSequence()
.map { it.stateKey } .map { it.stateKey }

View file

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.room.membership
import android.content.Context import android.content.Context
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.*
@ -30,6 +29,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -37,7 +37,7 @@ import javax.inject.Inject
*/ */
internal class RoomDisplayNameResolver @Inject constructor(private val context: Context, internal class RoomDisplayNameResolver @Inject constructor(private val context: Context,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val credentials: Credentials @UserId private val userId: String
) { ) {
/** /**
@ -79,7 +79,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
if (roomEntity?.membership == Membership.INVITE) { if (roomEntity?.membership == Membership.INVITE) {
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst() val inviteMeEvent = roomMembers.queryRoomMemberEvent(userId).findFirst()
val inviterId = inviteMeEvent?.sender val inviterId = inviteMeEvent?.sender
name = if (inviterId != null) { name = if (inviterId != null) {
val inviterMemberEvent = loadedMembers.where() val inviterMemberEvent = loadedMembers.where()
@ -97,7 +97,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
} }
} else { } else {
loadedMembers.where() loadedMembers.where()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId) .notEqualTo(EventEntityFields.STATE_KEY, userId)
.limit(3) .limit(3)
.findAll() .findAll()
} }

View file

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.session.room.prune package im.vector.matrix.android.internal.session.room.prune
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
@ -37,7 +37,7 @@ import javax.inject.Inject
* As it will actually delete the content, it should be called last in the list of listener. * As it will actually delete the content, it should be called last in the list of listener.
*/ */
internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
private val credentials: Credentials, @UserId private val userId: String,
private val pruneEventTask: PruneEventTask, private val pruneEventTask: PruneEventTask,
private val taskExecutor: TaskExecutor) : private val taskExecutor: TaskExecutor) :
RealmLiveEntityObserver<EventEntity>(realmConfiguration) { RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
@ -54,7 +54,7 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat
val params = PruneEventTask.Params( val params = PruneEventTask.Params(
insertedDomains, insertedDomains,
credentials.userId userId
) )
pruneEventTask.configureWith(params).executeBy(taskExecutor) pruneEventTask.configureWith(params).executeBy(taskExecutor)
} }

View file

@ -22,7 +22,6 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
@ -33,6 +32,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.query.isEventRead import im.vector.matrix.android.internal.database.query.isEventRead
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
@ -41,7 +41,7 @@ internal class DefaultReadService @AssistedInject constructor(@Assisted private
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
private val credentials: Credentials @UserId private val userId: String
) : ReadService { ) : ReadService {
@AssistedInject.Factory @AssistedInject.Factory
@ -78,7 +78,7 @@ internal class DefaultReadService @AssistedInject constructor(@Assisted private
override fun isEventRead(eventId: String): Boolean { override fun isEventRead(eventId: String): Boolean {
return isEventRead(monarchy, credentials.userId, roomId, eventId) return isEventRead(monarchy, userId, roomId, eventId)
} }
override fun getReadMarkerLive(): LiveData<Optional<String>> { override fun getReadMarkerLive(): LiveData<Optional<String>> {
@ -92,7 +92,7 @@ internal class DefaultReadService @AssistedInject constructor(@Assisted private
override fun getMyReadReceiptLive(): LiveData<Optional<String>> { override fun getMyReadReceiptLive(): LiveData<Optional<String>> {
val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm -> val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm ->
ReadReceiptEntity.where(realm, roomId = roomId, userId = credentials.userId) ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
} }
return Transformations.map(liveRealmData) { results -> return Transformations.map(liveRealmData) { results ->
Optional.from(results.firstOrNull()?.eventId) Optional.from(results.firstOrNull()?.eventId)

View file

@ -17,16 +17,15 @@
package im.vector.matrix.android.internal.session.room.read package im.vector.matrix.android.internal.session.room.read
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.*
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
@ -52,11 +51,11 @@ private const val READ_MARKER = "m.fully_read"
private const val READ_RECEIPT = "m.read" private const val READ_RECEIPT = "m.read"
internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI, internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI,
private val credentials: Credentials,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val roomFullyReadHandler: RoomFullyReadHandler, private val roomFullyReadHandler: RoomFullyReadHandler,
private val readReceiptHandler: ReadReceiptHandler private val readReceiptHandler: ReadReceiptHandler,
) : SetReadMarkersTask { @UserId private val userId: String)
: SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) { override suspend fun execute(params: SetReadMarkersTask.Params) {
val markers = HashMap<String, String>() val markers = HashMap<String, String>()
@ -84,7 +83,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
} }
if (readReceiptEventId != null if (readReceiptEventId != null
&& !isEventRead(params.roomId, readReceiptEventId)) { && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) {
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) { if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event $readReceiptEventId") Timber.w("Can't set read receipt for local event $readReceiptEventId")
} else { } else {
@ -108,12 +107,12 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId)) roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
} }
if (readReceiptId != null) { if (readReceiptId != null) {
val readReceiptContent = ReadReceiptHandler.createContent(credentials.userId, readReceiptId) val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId)
readReceiptHandler.handle(realm, roomId, readReceiptContent, false) readReceiptHandler.handle(realm, roomId, readReceiptContent, false)
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId
if (isLatestReceived) { if (isLatestReceived) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@awaitTransaction ?: return@awaitTransaction
roomSummary.notificationCount = 0 roomSummary.notificationCount = 0
roomSummary.highlightCount = 0 roomSummary.highlightCount = 0
roomSummary.hasUnreadMessages = false roomSummary.hasUnreadMessages = false
@ -133,19 +132,4 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
} }
} }
private fun isEventRead(roomId: String, eventId: String): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
val readReceipt = ReadReceiptEntity.where(realm, roomId, credentials.userId).findFirst()
?: return false
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
?: return false
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
?: Int.MAX_VALUE
eventToCheckIndex <= readReceiptIndex
}
}
} }

View file

@ -23,7 +23,6 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
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.Event
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
@ -37,6 +36,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.RedactEventWorker import im.vector.matrix.android.internal.session.room.send.RedactEventWorker
@ -50,7 +50,7 @@ import timber.log.Timber
internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String,
private val context: Context, private val context: Context,
private val credentials: Credentials, @UserId private val userId: String,
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
@ -111,7 +111,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
//TODO duplicate with send service? //TODO duplicate with send service?
private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest { private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest {
val sendContentWorkerParams = RedactEventWorker.Params( val sendContentWorkerParams = RedactEventWorker.Params(
credentials.userId, userId,
localEvent.eventId!!, localEvent.eventId!!,
roomId, roomId,
eventId, eventId,
@ -199,13 +199,13 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest { private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
// Same parameter // Same parameter
val params = EncryptEventWorker.Params(credentials.userId, roomId, event, keepKeys) val params = EncryptEventWorker.Params(userId, roomId, event, keepKeys)
val sendWorkData = WorkerParamsFactory.toData(params) val sendWorkData = WorkerParamsFactory.toData(params)
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true) return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
} }
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }

View file

@ -17,14 +17,10 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send
import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.work.* import androidx.work.*
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig
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.* import im.vector.matrix.android.api.session.events.model.*
@ -32,16 +28,16 @@ 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
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.DraftMapper
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
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
@ -59,7 +55,7 @@ private const val BACKOFF_DELAY = 10_000L
internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String,
private val context: Context, private val context: Context,
private val credentials: Credentials, @UserId private val userId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val monarchy: Monarchy private val monarchy: Monarchy
@ -292,7 +288,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
private fun createEncryptEventWork(event: Event, startChain: Boolean): 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(userId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(params) val sendWorkData = WorkerParamsFactory.toData(params)
return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>() return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
@ -304,7 +300,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
} }
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
@ -314,7 +310,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also { val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also {
saveLocalEcho(it) saveLocalEcho(it)
} }
val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, redactEvent.eventId!!, roomId, event.eventId, reason) val sendContentWorkerParams = RedactEventWorker.Params(userId, redactEvent.eventId!!, roomId, event.eventId, reason)
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true) return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
} }
@ -323,7 +319,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
attachment: ContentAttachmentData, attachment: ContentAttachmentData,
isRoomEncrypted: Boolean, isRoomEncrypted: Boolean,
startChain: Boolean): OneTimeWorkRequest { startChain: Boolean): OneTimeWorkRequest {
val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment, isRoomEncrypted) val uploadMediaWorkerParams = UploadContentWorker.Params(userId, roomId, event, attachment, isRoomEncrypted)
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
return matrixOneTimeWorkRequestBuilder<UploadContentWorker>() return matrixOneTimeWorkRequestBuilder<UploadContentWorker>()

View file

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.room.send
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.* import im.vector.matrix.android.api.session.events.model.*
@ -33,12 +32,13 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.internal.database.helper.addSendingEvent import im.vector.matrix.android.internal.database.helper.addSendingEvent
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.util.StringProvider
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import java.util.UUID import java.util.*
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -50,7 +50,7 @@ import javax.inject.Inject
* *
* The transactionID is used as loc * The transactionID is used as loc
*/ */
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, internal class LocalEchoEventFactory @Inject constructor(@UserId private val userId: String,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val roomSummaryUpdater: RoomSummaryUpdater) { private val roomSummaryUpdater: RoomSummaryUpdater) {
// TODO Inject // TODO Inject
@ -163,7 +163,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),
senderId = credentials.userId, senderId = userId,
eventId = localId, eventId = localId,
type = EventType.REACTION, type = EventType.REACTION,
content = content.toContent(), content = content.toContent(),
@ -255,7 +255,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),
senderId = credentials.userId, senderId = userId,
eventId = localID, eventId = localID,
type = EventType.MESSAGE, type = EventType.MESSAGE,
content = content.toContent(), content = content.toContent(),
@ -373,7 +373,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),
senderId = credentials.userId, senderId = userId,
eventId = localID, eventId = localID,
type = EventType.REDACTION, type = EventType.REDACTION,
redacts = eventId, redacts = eventId,

View file

@ -106,7 +106,7 @@ internal class TimelineEventDecryptor(
Timber.v("Successfully decrypted event ${eventId}") Timber.v("Successfully decrypted event ${eventId}")
eventEntity.setDecryptionResult(result) eventEntity.setDecryptionResult(result)
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
Timber.v("Failed to decrypte event ${eventId} ${e}") Timber.v("Failed to decrypt event ${eventId} ${e}")
if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
//Keep track of unknown sessions to automatically try to decrypt on new session //Keep track of unknown sessions to automatically try to decrypt on new session
eventEntity.decryptionErrorCode = e.errorType.name eventEntity.decryptionErrorCode = e.errorType.name

View file

@ -17,15 +17,11 @@
package im.vector.matrix.android.internal.session.signout package im.vector.matrix.android.internal.session.signout
import android.content.Context import android.content.Context
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.crypto.CryptoModule import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.di.*
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserCacheDirectory
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionModule import im.vector.matrix.android.internal.session.SessionModule
import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.session.cache.ClearCacheTask
@ -38,7 +34,7 @@ import javax.inject.Inject
internal interface SignOutTask : Task<Unit, Unit> internal interface SignOutTask : Task<Unit, Unit>
internal class DefaultSignOutTask @Inject constructor(private val context: Context, internal class DefaultSignOutTask @Inject constructor(private val context: Context,
private val credentials: Credentials, @UserId private val userId: String,
private val signOutAPI: SignOutAPI, private val signOutAPI: SignOutAPI,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
@ -55,13 +51,13 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte
} }
Timber.d("SignOut: release session...") Timber.d("SignOut: release session...")
sessionManager.releaseSession(credentials.userId) sessionManager.releaseSession(userId)
Timber.d("SignOut: cancel pending works...") Timber.d("SignOut: cancel pending works...")
WorkManagerUtil.cancelAllWorks(context) WorkManagerUtil.cancelAllWorks(context)
Timber.d("SignOut: delete session params...") Timber.d("SignOut: delete session params...")
sessionParamsStore.delete(credentials.userId) sessionParamsStore.delete(userId)
Timber.d("SignOut: clear session data...") Timber.d("SignOut: clear session data...")
clearSessionDataTask.execute(Unit) clearSessionDataTask.execute(Unit)

View file

@ -64,12 +64,13 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc
} }
} }
/** Note: [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] is observing changes */
realm.insertOrUpdate(groups) realm.insertOrUpdate(groups)
} }
private fun handleJoinedGroup(realm: Realm, private fun handleJoinedGroup(realm: Realm,
groupId: String): GroupEntity { groupId: String): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = Membership.JOIN groupEntity.membership = Membership.JOIN
return groupEntity return groupEntity
@ -77,21 +78,16 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc
private fun handleInvitedGroup(realm: Realm, private fun handleInvitedGroup(realm: Realm,
groupId: String): GroupEntity { groupId: String): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = Membership.INVITE groupEntity.membership = Membership.INVITE
return groupEntity return groupEntity
} }
// TODO : handle it
private fun handleLeftGroup(realm: Realm, private fun handleLeftGroup(realm: Realm,
groupId: String): GroupEntity { groupId: String): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = Membership.LEAVE groupEntity.membership = Membership.LEAVE
return groupEntity return groupEntity
} }
} }

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -87,11 +88,11 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private fun checkPushRules(roomsSyncResponse: RoomsSyncResponse) { private fun checkPushRules(roomsSyncResponse: RoomsSyncResponse) {
Timber.v("[PushRules] --> checkPushRules") Timber.v("[PushRules] --> checkPushRules")
if (tokenStore.getLastToken() == null) { if (tokenStore.getLastToken() == null) {
Timber.v("[PushRules] <-- No push tule check on initial sync") Timber.v("[PushRules] <-- No push rule check on initial sync")
return return
} //nothing on initial sync } //nothing on initial sync
val rules = pushRuleService.getPushRules("global") val rules = pushRuleService.getPushRules(RuleScope.GLOBAL)
processForPushTask.configureWith(ProcessEventForPushTask.Params(roomsSyncResponse, rules)) processForPushTask.configureWith(ProcessEventForPushTask.Params(roomsSyncResponse, rules))
.executeBy(taskExecutor) .executeBy(taskExecutor)
Timber.v("[PushRules] <-- Push task scheduled") Timber.v("[PushRules] <-- Push task scheduled")

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import arrow.core.Try
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
@ -33,73 +32,70 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
private val cryptoService: DefaultCryptoService, private val cryptoService: DefaultCryptoService,
private val initialSyncProgressService: DefaultInitialSyncProgressService) { private val initialSyncProgressService: DefaultInitialSyncProgressService) {
fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean) {
return Try { val isInitialSync = fromToken == null
val isInitialSync = fromToken == null Timber.v("Start handling sync, is InitialSync: $isInitialSync")
Timber.v("Start handling sync, is InitialSync: $isInitialSync") val reporter = initialSyncProgressService.takeIf { isInitialSync }
val reporter = initialSyncProgressService.takeIf { isInitialSync }
measureTimeMillis {
if (!cryptoService.isStarted()) {
Timber.v("Should start cryptoService")
cryptoService.start(isInitialSync)
}
}.also {
Timber.v("Finish handling start cryptoService in $it ms")
}
val measure = measureTimeMillis {
// Handle the to device events before the room ones
// to ensure to decrypt them properly
measureTimeMillis { measureTimeMillis {
if (!cryptoService.isStarted()) { Timber.v("Handle toDevice")
Timber.v("Should start cryptoService") reportSubtask(reporter, R.string.initial_sync_start_importing_account_crypto, 100, 0.1f) {
cryptoService.start(isInitialSync) if (syncResponse.toDevice != null) {
cryptoSyncHandler.handleToDevice(syncResponse.toDevice, reporter)
}
} }
}.also { }.also {
Timber.v("Finish handling start cryptoService in $it ms") Timber.v("Finish handling toDevice in $it ms")
} }
val measure = measureTimeMillis {
// Handle the to device events before the room ones measureTimeMillis {
// to ensure to decrypt them properly Timber.v("Handle rooms")
measureTimeMillis {
Timber.v("Handle toDevice") reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) {
reportSubtask(reporter, R.string.initial_sync_start_importing_account_crypto, 100, 0.1f) { if (syncResponse.rooms != null) {
if (syncResponse.toDevice != null) { roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter)
cryptoSyncHandler.handleToDevice(syncResponse.toDevice, reporter)
}
} }
}.also {
Timber.v("Finish handling toDevice in $it ms")
} }
}.also {
measureTimeMillis { Timber.v("Finish handling rooms in $it ms")
Timber.v("Handle rooms")
reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) {
if (syncResponse.rooms != null) {
roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter)
}
}
}.also {
Timber.v("Finish handling rooms in $it ms")
}
measureTimeMillis {
reportSubtask(reporter, R.string.initial_sync_start_importing_account_groups, 100, 0.1f) {
Timber.v("Handle groups")
if (syncResponse.groups != null) {
groupSyncHandler.handle(syncResponse.groups, reporter)
}
}
}.also {
Timber.v("Finish handling groups in $it ms")
}
measureTimeMillis {
reportSubtask(reporter, R.string.initial_sync_start_importing_account_data, 100, 0.1f) {
Timber.v("Handle accountData")
userAccountDataSyncHandler.handle(syncResponse.accountData, syncResponse.rooms?.invite)
}
}.also {
Timber.v("Finish handling accountData in $it ms")
}
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse)
} }
Timber.v("Finish handling sync in $measure ms")
syncResponse
measureTimeMillis {
reportSubtask(reporter, R.string.initial_sync_start_importing_account_groups, 100, 0.1f) {
Timber.v("Handle groups")
if (syncResponse.groups != null) {
groupSyncHandler.handle(syncResponse.groups, reporter)
}
}
}.also {
Timber.v("Finish handling groups in $it ms")
}
measureTimeMillis {
reportSubtask(reporter, R.string.initial_sync_start_importing_account_data, 100, 0.1f) {
Timber.v("Handle accountData")
userAccountDataSyncHandler.handle(syncResponse.accountData, syncResponse.rooms?.invite)
}
}.also {
Timber.v("Finish handling accountData in $it ms")
}
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse)
} }
Timber.v("Finish handling sync in $measure ms")
} }
} }

View file

@ -18,10 +18,10 @@ package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.filter.FilterRepository
@ -36,7 +36,7 @@ internal interface SyncTask : Task<SyncTask.Params, Unit> {
} }
internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
private val credentials: Credentials, @UserId private val userId: String,
private val filterRepository: FilterRepository, private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler, private val syncResponseHandler: SyncResponseHandler,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
@ -70,7 +70,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
// Intercept 401 // Intercept 401
if (throwable is Failure.ServerError if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) { && throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete(credentials.userId) sessionParamsStore.delete(userId)
} }
throw throwable throw throwable
} }

View file

@ -17,16 +17,18 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.getDirectRooms import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.pushers.SavePushRulesTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
@ -37,15 +39,17 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class UserAccountDataSyncHandler @Inject constructor(private val monarchy: Monarchy, internal class UserAccountDataSyncHandler @Inject constructor(private val monarchy: Monarchy,
private val credentials: Credentials, @UserId private val userId: String,
private val directChatsHelper: DirectChatsHelper, private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val savePushRulesTask: SavePushRulesTask,
private val taskExecutor: TaskExecutor) { private val taskExecutor: TaskExecutor) {
fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) { suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) {
accountData?.list?.forEach { accountData?.list?.forEach {
when (it) { when (it) {
is UserAccountDataDirectMessages -> handleDirectChatRooms(it) is UserAccountDataDirectMessages -> handleDirectChatRooms(it)
is UserAccountDataPushRules -> handlePushRules(it)
else -> return@forEach else -> return@forEach
} }
} }
@ -54,6 +58,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
} }
} }
private suspend fun handlePushRules(userAccountDataPushRules: UserAccountDataPushRules) {
savePushRulesTask.execute(SavePushRulesTask.Params(userAccountDataPushRules.content))
}
private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) { private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
@ -81,11 +89,11 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
val directChats = directChatsHelper.getLocalUserAccount() val directChats = directChatsHelper.getLocalUserAccount()
var hasUpdate = false var hasUpdate = false
invites.forEach { (roomId, _) -> invites.forEach { (roomId, _) ->
val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(credentials.userId) val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(userId)
val inviterId = myUserStateEvent?.sender val inviterId = myUserStateEvent?.sender
val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() } val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
val isDirect = myUserRoomMember?.isDirect val isDirect = myUserRoomMember?.isDirect
if (inviterId != null && inviterId != credentials.userId && isDirect == true) { if (inviterId != null && inviterId != userId && isDirect == true) {
directChats directChats
.getOrPut(inviterId, { arrayListOf() }) .getOrPut(inviterId, { arrayListOf() })
.apply { .apply {

View file

@ -30,7 +30,6 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import kotlinx.coroutines.CancellationException
import timber.log.Timber import timber.log.Timber
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -140,7 +139,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
// Timeout are not critical // Timeout are not critical
Timber.v("Timeout") Timber.v("Timeout")
} else if (failure is Failure.Unknown && failure.throwable is CancellationException) { } else if (failure is Failure.Cancelled) {
Timber.v("Cancelled") Timber.v("Cancelled")
} else if (failure is Failure.ServerError } else if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) { && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {

View file

@ -23,5 +23,6 @@ internal interface UserAccountData {
const val TYPE_DIRECT_MESSAGES = "m.direct" const val TYPE_DIRECT_MESSAGES = "m.direct"
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls" const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
const val TYPE_WIDGETS = "m.widgets" const val TYPE_WIDGETS = "m.widgets"
const val TYPE_PUSH_RULES = "m.push_rules"
} }
} }

View file

@ -0,0 +1,27 @@
/*
* 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.session.sync.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
@JsonClass(generateAdapter = true)
internal data class UserAccountDataPushRules(
@Json(name = "content") val content: GetPushRulesResponse
) : UserAccountData

View file

@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.session.user.accountdata package im.vector.matrix.android.internal.session.user.accountdata
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -42,11 +42,11 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
} }
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI, internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
private val credentials: Credentials) : UpdateUserAccountDataTask { @UserId private val userId: String) : UpdateUserAccountDataTask {
override suspend fun execute(params: UpdateUserAccountDataTask.Params) { override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
return executeRequest { return executeRequest {
apiCall = accountDataApi.setAccountData(credentials.userId, params.type, params.getData()) apiCall = accountDataApi.setAccountData(userId, params.type, params.getData())
} }
} }

View file

@ -24,14 +24,13 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -164,10 +163,29 @@ class PushrulesConditionTest {
} }
@Test
fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice")
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.notice", "A").toContent(),
originServerTs = 0,
roomId = "2joined").also {
Assert.assertTrue("Notice", conditionEqual.isSatisfied(it))
}
}
class MockRoomService() : RoomService { class MockRoomService() : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>) { override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun getRoom(roomId: String): Room? { override fun getRoom(roomId: String): Room? {
@ -184,7 +202,54 @@ class PushrulesConditionTest {
} }
class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room { class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room {
override fun getTimeLineEventLive(eventId: String): LiveData<TimelineEvent> { override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deleteFailedEcho(localEcho: TimelineEvent) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun clearSendingQueue() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun resendAllFailedMessages() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun saveDraft(draft: UserDraft) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deleteDraft() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getDraftsLive(): LiveData<List<UserDraft>> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getStateEvent(eventType: String): Event? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun editReply(replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
}
override fun liveTimeLineEvent(eventId: String): LiveData<TimelineEvent> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
@ -193,7 +258,7 @@ class PushrulesConditionTest {
return _numberOfJoinedMembers return _numberOfJoinedMembers
} }
override fun getRoomSummaryLive(): LiveData<RoomSummary> { override fun liveRoomSummary(): LiveData<RoomSummary> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
@ -201,7 +266,7 @@ class PushrulesConditionTest {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline { override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
@ -245,7 +310,7 @@ class PushrulesConditionTest {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun loadRoomMembersIfNeeded(): Cancelable { override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
@ -257,15 +322,15 @@ class PushrulesConditionTest {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun invite(userId: String, callback: MatrixCallback<Unit>) { override fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun join(callback: MatrixCallback<Unit>) { override fun join(viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun leave(callback: MatrixCallback<Unit>) { override fun leave(callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }

View file

@ -15,7 +15,7 @@ androidExtensions {
} }
ext.versionMajor = 0 ext.versionMajor = 0
ext.versionMinor = 6 ext.versionMinor = 7
ext.versionPatch = 0 ext.versionPatch = 0
static def getGitTimestamp() { static def getGitTimestamp() {

View file

@ -21,11 +21,19 @@ import android.content.Intent
import android.os.Build import android.os.Build
import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.NotificationUtils
import timber.log.Timber import timber.log.Timber
class VectorSyncService : SyncService() { class VectorSyncService : SyncService() {
private lateinit var notificationUtils: NotificationUtils
override fun onCreate() {
super.onCreate()
notificationUtils = vectorComponent().notificationUtils()
}
override fun onDestroy() { override fun onDestroy() {
removeForegroundNotif() removeForegroundNotif()
super.onDestroy() super.onDestroy()
@ -43,7 +51,7 @@ class VectorSyncService : SyncService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.v("VectorSyncService - onStartCommand ") Timber.v("VectorSyncService - onStartCommand ")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false) val notification = notificationUtils.buildForegroundServiceNotification(R.string.notification_listening_for_events, false)
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
} }
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)

View file

@ -27,6 +27,7 @@ import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.EmojiCompatWrapper import im.vector.riotx.EmojiCompatWrapper
import im.vector.riotx.VectorApplication import im.vector.riotx.VectorApplication
import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.pushers.PushersManager
import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
@ -63,6 +64,8 @@ interface VectorComponent {
fun resources(): Resources fun resources(): Resources
fun dimensionUtils(): DimensionConverter
fun vectorConfiguration(): VectorConfiguration fun vectorConfiguration(): VectorConfiguration
fun avatarRenderer(): AvatarRenderer fun avatarRenderer(): AvatarRenderer

View file

@ -32,7 +32,6 @@ fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener)
startSync(isAtLeastStarted) startSync(isAtLeastStarted)
refreshPushers() refreshPushers()
pushRuleTriggerListener.startWithSession(this) pushRuleTriggerListener.startWithSession(this)
fetchPushRules()
// TODO P1 From HomeActivity // TODO P1 From HomeActivity
// @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler // @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler

View file

@ -15,25 +15,26 @@
*/ */
package im.vector.riotx.core.utils package im.vector.riotx.core.utils
import android.content.Context import android.content.res.Resources
import android.util.TypedValue import android.util.TypedValue
import javax.inject.Inject
object DimensionUtils { class DimensionConverter @Inject constructor(val resources: Resources) {
fun dpToPx(dp: Int, context: Context): Int { fun dpToPx(dp: Int): Int {
return TypedValue.applyDimension( return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(), dp.toFloat(),
context.resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
} }
fun spToPx(sp: Int, context: Context): Int { fun spToPx(sp: Int): Int {
return TypedValue.applyDimension( return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
sp.toFloat(), sp.toFloat(),
context.resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
} }
} }

View file

@ -204,7 +204,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
Runnable { Runnable {
alert.weakCurrentActivity?.get()?.let { alert.weakCurrentActivity?.get()?.let {
val intent = SASVerificationActivity.outgoingIntent(it, val intent = SASVerificationActivity.outgoingIntent(it,
session?.sessionParams?.credentials?.userId ?: "", session?.myUserId ?: "",
userId, deviceId) userId, deviceId)
it.startActivity(intent) it.startActivity(intent)
} }

View file

@ -60,7 +60,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
.apply { .apply {
contentAction = Runnable { contentAction = Runnable {
val intent = SASVerificationActivity.incomingIntent(context, val intent = SASVerificationActivity.incomingIntent(context,
session?.sessionParams?.credentials?.userId ?: "", session?.myUserId ?: "",
tx.otherUserId, tx.otherUserId,
tx.transactionId) tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) weakCurrentActivity?.get()?.startActivity(intent)
@ -78,7 +78,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
context.getString(R.string.action_open), context.getString(R.string.action_open),
Runnable { Runnable {
val intent = SASVerificationActivity.incomingIntent(context, val intent = SASVerificationActivity.incomingIntent(context,
session?.sessionParams?.credentials?.userId ?: "", session?.myUserId ?: "",
tx.otherUserId, tx.otherUserId,
tx.transactionId) tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) weakCurrentActivity?.get()?.startActivity(intent)

View file

@ -36,7 +36,7 @@ import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.extensions.setupAsSearch
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.DimensionUtils import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.fragment_create_direct_room.* import kotlinx.android.synthetic.main.fragment_create_direct_room.*
import javax.inject.Inject import javax.inject.Inject
@ -51,6 +51,7 @@ class CreateDirectRoomKnownUsersFragment : VectorBaseFragment(), KnownUsersContr
@Inject lateinit var directRoomController: KnownUsersController @Inject lateinit var directRoomController: KnownUsersController
@Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var dimensionConverter: DimensionConverter
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
@ -156,7 +157,7 @@ class CreateDirectRoomKnownUsersFragment : VectorBaseFragment(), KnownUsersContr
private fun addChipToGroup(user: User, chipGroup: ChipGroup) { private fun addChipToGroup(user: User, chipGroup: ChipGroup) {
val chip = Chip(requireContext()) val chip = Chip(requireContext())
chip.setChipBackgroundColorResource(android.R.color.transparent) chip.setChipBackgroundColorResource(android.R.color.transparent)
chip.chipStrokeWidth = DimensionUtils.dpToPx(1, requireContext()).toFloat() chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat()
chip.text = if (user.displayName.isNullOrBlank()) user.userId else user.displayName chip.text = if (user.displayName.isNullOrBlank()) user.userId else user.displayName
chip.isClickable = true chip.isClickable = true
chip.isCheckable = false chip.isCheckable = false

View file

@ -26,6 +26,7 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
@ -93,20 +94,20 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
session session
.rx() .rx()
.liveGroupSummaries() .liveGroupSummaries()
// Keep only joined groups. Group invitations will be managed later
.map { it.filter { groupSummary -> groupSummary.membership == Membership.JOIN } }
.map { .map {
val myUser = session.getUser(session.myUserId) val myUser = session.getUser(session.myUserId)
val allCommunityGroup = GroupSummary( val allCommunityGroup = GroupSummary(
groupId = ALL_COMMUNITIES_GROUP_ID, groupId = ALL_COMMUNITIES_GROUP_ID,
membership = Membership.JOIN,
displayName = stringProvider.getString(R.string.group_all_communities), displayName = stringProvider.getString(R.string.group_all_communities),
avatarUrl = myUser?.avatarUrl ?: "") avatarUrl = myUser?.avatarUrl ?: "")
listOf(allCommunityGroup) + it listOf(allCommunityGroup) + it
} }
.execute { async -> .execute { async ->
// TODO Phase2 Handle the case where the selected group is deleted on another client
val newSelectedGroup = selectedGroup ?: async()?.firstOrNull() val newSelectedGroup = selectedGroup ?: async()?.firstOrNull()
copy(asyncGroups = async, selectedGroup = newSelectedGroup) copy(asyncGroups = async, selectedGroup = newSelectedGroup)
} }
} }
} }

View file

@ -0,0 +1,82 @@
/*
* 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.riotx.features.home.room.detail.timeline.factory
import android.view.View
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.toModel
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject
class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer,
private val avatarSizeProvider: AvatarSizeProvider) {
fun create(event: TimelineEvent,
highlight: Boolean,
callback: TimelineEventController.Callback?): NoticeItem? {
val text = buildNoticeText(event.root, event.senderName) ?: return null
val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
avatarUrl = event.senderAvatar(),
memberName = event.senderName(),
showInformation = false
)
val attributes = NoticeItem.Attributes(
avatarRenderer = avatarRenderer,
informationData = informationData,
noticeText = text,
itemLongClickListener = View.OnLongClickListener { view ->
callback?.onEventLongClicked(informationData, null, view) ?: false
},
readReceiptsCallback = callback
)
return NoticeItem_()
.leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(highlight)
.attributes(attributes)
}
private fun buildNoticeText(event: Event, senderName: String?): CharSequence? {
return when {
EventType.ENCRYPTION == event.getClearType() -> {
val content = event.content.toModel<EncryptionEventContent>() ?: return null
stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm)
}
else -> null
}
}
}

View file

@ -27,12 +27,10 @@ import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject import javax.inject.Inject
class NoticeItemFactory @Inject constructor( class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
private val eventFormatter: NoticeEventFormatter, private val avatarRenderer: AvatarRenderer,
private val avatarRenderer: AvatarRenderer, private val informationDataFactory: MessageInformationDataFactory,
private val informationDataFactory: MessageInformationDataFactory, private val avatarSizeProvider: AvatarSizeProvider) {
private val avatarSizeProvider: AvatarSizeProvider
) {
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
highlight: Boolean, highlight: Boolean,

View file

@ -16,20 +16,19 @@
package im.vector.riotx.features.home.room.detail.timeline.helper package im.vector.riotx.features.home.room.detail.timeline.helper
import androidx.appcompat.app.AppCompatActivity import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
import javax.inject.Inject import javax.inject.Inject
class AvatarSizeProvider @Inject constructor(private val context: AppCompatActivity) { class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
private val avatarStyle = AvatarStyle.SMALL private val avatarStyle = AvatarStyle.SMALL
val leftGuideline: Int by lazy { val leftGuideline: Int by lazy {
dpToPx(avatarStyle.avatarSizeDP + 8, context) dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 8)
} }
val avatarSize: Int by lazy { val avatarSize: Int by lazy {
dpToPx(avatarStyle.avatarSizeDP, context) dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
} }
companion object { companion object {

View file

@ -26,8 +26,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.platform.CheckableView import im.vector.riotx.core.platform.CheckableView
import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.ui.views.ReadMarkerView
import im.vector.riotx.core.ui.views.ReadReceiptsView import im.vector.riotx.core.ui.views.ReadReceiptsView
import im.vector.riotx.core.utils.DimensionUtils.dpToPx import im.vector.riotx.core.utils.DimensionConverter
import org.w3c.dom.Attr
/** /**
* Children must override getViewType() * Children must override getViewType()
@ -40,6 +39,9 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
@EpoxyAttribute @EpoxyAttribute
open var leftGuideline: Int = 0 open var leftGuideline: Int = 0
@EpoxyAttribute
lateinit var dimensionConverter: DimensionConverter
override fun bind(holder: H) { override fun bind(holder: H) {
super.bind(holder) super.bind(holder)
holder.leftGuideline.setGuidelineBegin(leftGuideline) holder.leftGuideline.setGuidelineBegin(leftGuideline)

View file

@ -24,7 +24,6 @@ import androidx.core.widget.TextViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.utils.containsOnlyEmojis
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.html.PillImageSpan
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -39,6 +38,8 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
var message: CharSequence? = null var message: CharSequence? = null
@EpoxyAttribute @EpoxyAttribute
var useBigFont: Boolean = false
@EpoxyAttribute
var urlClickCallback: TimelineEventController.UrlClickCallback? = null var urlClickCallback: TimelineEventController.UrlClickCallback? = null
// Better link movement methods fixes the issue when // Better link movement methods fixes the issue when
@ -65,9 +66,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
super.bind(holder) super.bind(holder)
holder.messageView.movementMethod = mvmtMethod holder.messageView.movementMethod = mvmtMethod
if (useBigFont) {
val msg = message ?: ""
if (msg.length <= 4 && containsOnlyEmojis(msg.toString())) {
holder.messageView.textSize = 44F holder.messageView.textSize = 44F
} else { } else {
holder.messageView.textSize = 14F holder.messageView.textSize = 14F

View file

@ -63,6 +63,18 @@ class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSe
} }
} }
override fun transitionToEnd() {
super.transitionToEnd()
createRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close)
}
override fun transitionToStart() {
super.transitionToStart()
createRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open)
}
fun show() { fun show() {
isVisible = true isVisible = true
createRoomButton.show() createRoomButton.show()

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.login
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
@ -68,12 +69,19 @@ class LoginFragment : VectorBaseFragment() {
homeServerField.focusChanges() homeServerField.focusChanges()
.subscribe { .subscribe {
if (!it) { if (!it) {
// TODO Also when clicking on button?
viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString())) viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString()))
} }
} }
.disposeOnDestroy() .disposeOnDestroy()
homeServerField.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString()))
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
val initHsUrl = viewModel.getInitialHomeServerUrl() val initHsUrl = viewModel.getInitialHomeServerUrl()
if (initHsUrl != null) { if (initHsUrl != null) {
homeServerField.setText(initHsUrl) homeServerField.setText(initHsUrl)
@ -170,6 +178,10 @@ class LoginFragment : VectorBaseFragment() {
passwordContainer.isVisible = true passwordContainer.isVisible = true
authenticateButton.isVisible = true authenticateButton.isVisible = true
authenticateButtonSso.isVisible = false authenticateButtonSso.isVisible = false
if (loginField.text.isNullOrBlank() && passwordField.text.isNullOrBlank()) {
//Jump focus to login
loginField.requestFocus()
}
} }
LoginMode.Sso -> { LoginMode.Sso -> {
loginField.isVisible = false loginField.isVisible = false

View file

@ -137,16 +137,23 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
private fun handleUpdateHomeserver(action: LoginActions.UpdateHomeServer) { private fun handleUpdateHomeserver(action: LoginActions.UpdateHomeServer) = withState { state ->
currentTask?.cancel()
var newConfig : HomeServerConnectionConfig? = null
Try { Try {
val homeServerUri = action.homeServerUrl val homeServerUri = action.homeServerUrl
homeServerConnectionConfig = HomeServerConnectionConfig.Builder() newConfig = HomeServerConnectionConfig.Builder()
.withHomeServerUri(homeServerUri) .withHomeServerUri(homeServerUri)
.build() .build()
} }
//Do not retry if we already have flows for this config -> causes infinite focus loop
if (newConfig?.homeServerUri?.toString() == homeServerConnectionConfig?.homeServerUri?.toString()
&& state.asyncHomeServerLoginFlowRequest is Success) return@withState
currentTask?.cancel()
homeServerConnectionConfig = newConfig
val homeServerConnectionConfigFinal = homeServerConnectionConfig val homeServerConnectionConfigFinal = homeServerConnectionConfig
if (homeServerConnectionConfigFinal == null) { if (homeServerConnectionConfigFinal == null) {

View file

@ -32,13 +32,14 @@ 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.glide.GlideRequest
import im.vector.riotx.core.utils.DimensionUtils.dpToPx import im.vector.riotx.core.utils.DimensionConverter
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) { class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val dimensionConverter: DimensionConverter) {
@Parcelize @Parcelize
data class Data( data class Data(
@ -67,10 +68,12 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
val (width, height) = processSize(data, mode) val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height imageView.layoutParams.height = height
imageView.layoutParams.width = width imageView.layoutParams.width = width
// a11y
imageView.contentDescription = data.filename
createGlideRequest(data, mode, imageView, width, height) createGlideRequest(data, mode, imageView, width, height)
.dontAnimate() .dontAnimate()
.transform(RoundedCorners(dpToPx(8, imageView.context))) .transform(RoundedCorners(dimensionConverter.dpToPx(8)))
.thumbnail(0.3f) .thumbnail(0.3f)
.into(imageView) .into(imageView)
@ -79,6 +82,9 @@ 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)
// a11y
imageView.contentDescription = data.filename
createGlideRequest(data, mode, imageView, width, height) createGlideRequest(data, mode, imageView, width, height)
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, override fun onLoadFailed(e: GlideException?,
@ -126,6 +132,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
} }
fun render(data: Data, imageView: BigImageView) { fun render(data: Data, imageView: BigImageView) {
// a11y
imageView.contentDescription = data.filename
val (width, height) = processSize(data, Mode.THUMBNAIL) val (width, height) = processSize(data, Mode.THUMBNAIL)
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val fullSize = contentUrlResolver.resolveFullSize(data.url) val fullSize = contentUrlResolver.resolveFullSize(data.url)

View file

@ -19,23 +19,21 @@ import im.vector.matrix.android.api.pushrules.Action
data class NotificationAction( data class NotificationAction(
val shouldNotify: Boolean, val shouldNotify: Boolean,
val highlight: Boolean = false, val highlight: Boolean,
val soundName: String? = null val soundName: String?
) { )
companion object {
fun extractFrom(ruleActions: List<Action>): NotificationAction { fun List<Action>.toNotificationAction(): NotificationAction {
var shouldNotify = false var shouldNotify = false
var highlight = false var highlight = false
var sound: String? = null var sound: String? = null
ruleActions.forEach { forEach { action ->
if (it.type == Action.Type.NOTIFY) shouldNotify = true when (action) {
if (it.type == Action.Type.DONT_NOTIFY) shouldNotify = false is Action.Notify -> shouldNotify = true
if (it.type == Action.Type.SET_TWEAK) { is Action.DoNotNotify -> shouldNotify = false
if (it.tweak_action == "highlight") highlight = it.boolValue ?: false is Action.Highlight -> highlight = action.highlight
if (it.tweak_action == "sound") sound = it.stringValue is Action.Sound -> sound = action.sound
}
}
return NotificationAction(shouldNotify, highlight, sound)
} }
} }
} return NotificationAction(shouldNotify, highlight, sound)
}

View file

@ -40,7 +40,7 @@ class PushRuleTriggerListener @Inject constructor(
Timber.e("Called without active session") Timber.e("Called without active session")
return return
} }
val notificationAction = NotificationAction.extractFrom(actions) val notificationAction = actions.toNotificationAction()
if (notificationAction.shouldNotify) { if (notificationAction.shouldNotify) {
val notifiableEvent = resolver.resolveEvent(event, session!!) val notifiableEvent = resolver.resolveEvent(event, session!!)
if (notifiableEvent == null) { if (notifiableEvent == null) {

View file

@ -21,6 +21,7 @@ import androidx.preference.Preference
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
@ -45,12 +46,13 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
if (mRuleMaster == null) { if (mRuleMaster == null) {
// The home server does not support RULE_ID_DISABLE_ALL, so hide the preference
pref.isVisible = false pref.isVisible = false
return return
} }
val areNotifEnabledAtAccountLevelt = !mRuleMaster.enabled val areNotifEnabledAtAccountLevel = !mRuleMaster.enabled
(pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevelt (pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevel
} }
} }
@ -114,19 +116,21 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
?.let { ?.let {
//Trick, we must enable this room to disable notifications //Trick, we must enable this room to disable notifications
pushRuleService.updatePushRuleEnableStatus("override", it, !switchPref.isChecked, pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE,
object : MatrixCallback<Unit> { it,
!switchPref.isChecked,
object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
pushRuleService.fetchPushRules() // Push rules will be updated form the sync
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
//revert the check box //revert the check box
switchPref.isChecked = !switchPref.isChecked switchPref.isChecked = !switchPref.isChecked
Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show()
} }
}) })
} }
} }

View file

@ -26,11 +26,11 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.matrix.android.api.pushrules.Action import im.vector.matrix.android.api.pushrules.getActions
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.features.notifications.NotificationAction import im.vector.riotx.features.notifications.toNotificationAction
@EpoxyModelClass(layout = R.layout.item_pushrule_raw) @EpoxyModelClass(layout = R.layout.item_pushrule_raw)
@ -50,12 +50,12 @@ abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
holder.view.setBackgroundColor(ContextCompat.getColor(context, R.color.vector_silver_color)) holder.view.setBackgroundColor(ContextCompat.getColor(context, R.color.vector_silver_color))
holder.ruleId.text = "[Disabled] ${pushRule.ruleId}" holder.ruleId.text = "[Disabled] ${pushRule.ruleId}"
} }
val actions = Action.mapFrom(pushRule) val actions = pushRule.getActions()
if (actions.isNullOrEmpty()) { if (actions.isEmpty()) {
holder.actionIcon.isInvisible = true holder.actionIcon.isInvisible = true
} else { } else {
holder.actionIcon.isVisible = true holder.actionIcon.isVisible = true
val notifAction = NotificationAction.extractFrom(actions) val notifAction = actions.toNotificationAction()
if (notifAction.shouldNotify && !notifAction.soundName.isNullOrBlank()) { if (notifAction.shouldNotify && !notifAction.soundName.isNullOrBlank()) {
holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_noisy)) holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_noisy))

View file

@ -17,6 +17,7 @@ package im.vector.riotx.features.settings.troubleshoot
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
@ -45,8 +46,7 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String
override fun doFix() { override fun doFix() {
if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished
// TODO Use constant for kind session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled,
session.updatePushRuleEnableStatus("override", defaultRule, !defaultRule.enabled,
object : MatrixCallback<Unit> { object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {

View file

@ -15,12 +15,12 @@
*/ */
package im.vector.riotx.features.settings.troubleshoot package im.vector.riotx.features.settings.troubleshoot
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.matrix.android.api.pushrules.getActions
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.notifications.NotificationAction import im.vector.riotx.features.notifications.toNotificationAction
import javax.inject.Inject import javax.inject.Inject
class TestBingRulesSettings @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, class TestBingRulesSettings @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
@ -29,15 +29,15 @@ class TestBingRulesSettings @Inject constructor(private val activeSessionHolder:
private val testedRules = private val testedRules =
listOf(RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME, listOf(RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME,
RuleIds.RULE_ID_CONTAIN_USER_NAME, RuleIds.RULE_ID_CONTAIN_USER_NAME,
RuleIds.RULE_ID_ONE_TO_ONE_ROOM, RuleIds.RULE_ID_ONE_TO_ONE_ROOM,
RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS) RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS)
val ruleSettingsName = arrayOf(R.string.settings_containing_my_display_name, val ruleSettingsName = arrayOf(R.string.settings_containing_my_display_name,
R.string.settings_containing_my_user_name, R.string.settings_containing_my_user_name,
R.string.settings_messages_in_one_to_one, R.string.settings_messages_in_one_to_one,
R.string.settings_messages_in_group_chat) R.string.settings_messages_in_group_chat)
override fun perform() { override fun perform() {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
@ -50,8 +50,8 @@ class TestBingRulesSettings @Inject constructor(private val activeSessionHolder:
var oneOrMoreRuleAreSilent = false var oneOrMoreRuleAreSilent = false
for ((index, ruleId) in testedRules.withIndex()) { for ((index, ruleId) in testedRules.withIndex()) {
pushRules.find { it.ruleId == ruleId }?.let { rule -> pushRules.find { it.ruleId == ruleId }?.let { rule ->
val actions = Action.mapFrom(rule) ?: return@let val actions = rule.getActions()
val notifAction = NotificationAction.extractFrom(actions) val notifAction = actions.toNotificationAction()
if (!rule.enabled || !notifAction.shouldNotify) { if (!rule.enabled || !notifAction.shouldNotify) {
//off //off
oneOrMoreRuleIsOff = true oneOrMoreRuleIsOff = true

View file

@ -25,6 +25,7 @@
android:id="@+id/groupToolbarAvatarImageView" android:id="@+id/groupToolbarAvatarImageView"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:contentDescription="@string/a11y_open_drawer"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView

View file

@ -104,6 +104,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textUri" android:inputType="textUri"
android:imeOptions="actionDone"
android:maxLines="1" /> android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>

View file

@ -30,6 +30,8 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center" android:scaleType="center"
android:src="@drawable/ic_fab_add_chat" android:src="@drawable/ic_fab_add_chat"
android:visibility="gone" android:visibility="gone"
@ -45,6 +47,8 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room" android:src="@drawable/ic_fab_add_room"
android:visibility="gone" android:visibility="gone"
app:maxImageSize="32dp" app:maxImageSize="32dp"

View file

@ -71,6 +71,7 @@
android:layout_width="22dp" android:layout_width="22dp"
android:layout_height="22dp" android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_close"
android:src="@drawable/ic_close_round" android:src="@drawable/ic_close_round"
android:tint="@color/riotx_notice" android:tint="@color/riotx_notice"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />
@ -88,6 +89,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/send_attachment"
android:src="@drawable/ic_attachment" android:src="@drawable/ic_attachment"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />
@ -107,6 +109,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/send"
android:src="@drawable/ic_send" android:src="@drawable/ic_send"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />

View file

@ -13,7 +13,10 @@
android:id="@+id/createRoomTouchGuard" android:id="@+id/createRoomTouchGuard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?riotx_touch_guard_bg" /> android:background="?riotx_touch_guard_bg"
android:clickable="true"
android:contentDescription="@string/a11y_create_menu_close"
android:focusable="true" />
<!-- Sub menu item 2 --> <!-- Sub menu item 2 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
@ -21,6 +24,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room" android:src="@drawable/ic_fab_add_room"
app:backgroundTint="#FFFFFF" app:backgroundTint="#FFFFFF"
app:fabCustomSize="48dp" app:fabCustomSize="48dp"
@ -35,6 +40,7 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:ellipsize="end" android:ellipsize="end"
android:importantForAccessibility="no"
android:text="@string/fab_menu_create_room" /> android:text="@string/fab_menu_create_room" />
<!-- Sub menu item 1 --> <!-- Sub menu item 1 -->
@ -43,6 +49,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:accessibilityTraversalBefore="@+id/createRoomItemGroup"
android:contentDescription="@string/a11y_create_direct_message"
android:src="@drawable/ic_fab_add_chat" android:src="@drawable/ic_fab_add_chat"
app:backgroundTint="#FFFFFF" app:backgroundTint="#FFFFFF"
app:fabCustomSize="48dp" app:fabCustomSize="48dp"
@ -57,6 +65,7 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:ellipsize="end" android:ellipsize="end"
android:importantForAccessibility="no"
android:text="@string/fab_menu_create_chat" /> android:text="@string/fab_menu_create_chat" />
<!-- Menu --> <!-- Menu -->
@ -64,6 +73,8 @@
android:id="@+id/createRoomButton" android:id="@+id/createRoomButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:accessibilityTraversalBefore="@+id/createRoomItemChat"
android:contentDescription="@string/a11y_create_menu_open"
android:src="@drawable/ic_fab_add" android:src="@drawable/ic_fab_add"
app:maxImageSize="14dp" /> app:maxImageSize="14dp" />

View file

@ -106,6 +106,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:contentDescription="@string/a11y_close_keys_backup_banner"
android:src="@drawable/ic_small_close" android:src="@drawable/ic_small_close"
app:layout_constraintEnd_toEndOf="@id/view_keys_backup_banner_close" app:layout_constraintEnd_toEndOf="@id/view_keys_backup_banner_close"
app:layout_constraintStart_toStartOf="@id/view_keys_backup_banner_close" app:layout_constraintStart_toStartOf="@id/view_keys_backup_banner_close"

View file

@ -3,23 +3,20 @@
<item <item
android:id="@+id/bottom_action_home" android:id="@+id/bottom_action_home"
android:contentDescription="@string/bottom_action_home"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_home_bottom_catchup" android:icon="@drawable/ic_home_bottom_catchup"
android:title="" /> android:title="@string/bottom_action_home" />
<item <item
android:id="@+id/bottom_action_people" android:id="@+id/bottom_action_people"
android:contentDescription="@string/bottom_action_people_x"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_home_bottom_chat" android:icon="@drawable/ic_home_bottom_chat"
android:title="" /> android:title="@string/bottom_action_people_x" />
<item <item
android:id="@+id/bottom_action_rooms" android:id="@+id/bottom_action_rooms"
android:contentDescription="@string/bottom_action_rooms"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_home_bottom_group" android:icon="@drawable/ic_home_bottom_group"
android:title="" /> android:title="@string/bottom_action_rooms" />
</menu> </menu>

View file

@ -13,4 +13,13 @@
<string name="error_network_timeout">Looks like the server is taking to long to respond, this can be caused by either poor connectivity or an error with our servers. Please try again in a while.</string> <string name="error_network_timeout">Looks like the server is taking to long to respond, this can be caused by either poor connectivity or an error with our servers. Please try again in a while.</string>
<string name="send_attachment">Send attachment</string>
<string name="a11y_open_drawer">Open the navigation drawer</string>
<string name="a11y_create_menu_open">Open the create room menu</string>
<string name="a11y_create_menu_close">Close the create room menu…</string>
<string name="a11y_create_direct_message">Create a new direct conversation</string>
<string name="a11y_create_room">Create a new room</string>
<string name="a11y_close_keys_backup_banner">Close keys backup banner</string>
</resources> </resources>