diff --git a/changelog.d/4090.feature b/changelog.d/4090.feature new file mode 100644 index 0000000000..be8aa317a6 --- /dev/null +++ b/changelog.d/4090.feature @@ -0,0 +1 @@ +Handle Presence support, for Direct Message room \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 98d561815b..23538c5285 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,3 +23,6 @@ vector.debugPrivateData=false # httpLogLevel values: NONE, BASIC, HEADERS, BODY vector.httpLogLevel=BASIC +# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above +#vector.debugPrivateData=true +#vector.httpLogLevel=BODY \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index b58829bb81..03d1ff69db 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -133,4 +133,8 @@ <color name="vctr_voice_message_toast_background_light">@color/palette_black_900</color> <color name="vctr_voice_message_toast_background_dark">@color/palette_gray_400</color> + <!-- Presence Indicator colors --> + <attr name="vctr_presence_indicator_offline" format="color" /> + <color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color> + <color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color> </resources> diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index f83953a527..d07e3c5297 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -42,6 +42,9 @@ <item name="vctr_markdown_block_background_color">@android:color/black</item> <item name="vctr_spoiler_background_color">#FFFFFFFF</item> + <!-- Presence Indicator colors --> + <item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_dark</item> + <!-- Some alias --> <item name="vctr_header_background">?vctr_system</item> <item name="vctr_list_separator">?vctr_content_quinary</item> diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index cd5e17d607..14ec372f28 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -42,6 +42,9 @@ <item name="vctr_markdown_block_background_color">#FFEEEEEE</item> <item name="vctr_spoiler_background_color">#FF000000</item> + <!-- Presence Indicator colors --> + <item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_light</item> + <!-- Some alias --> <item name="vctr_header_background">?vctr_system</item> <item name="vctr_list_separator">?vctr_content_quinary</item> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 124d7d9dae..d4bfd4ee8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -76,6 +77,7 @@ interface Session : TermsService, EventService, ProfileService, + PresenceService, PushRuleService, PushersService, SyncStatusService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 96b44ce8c9..169f90dbca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.session.presence.model.PresenceContent import timber.log.Timber typealias Content = JsonDict @@ -305,3 +306,7 @@ fun Event.isReply(): Boolean { fun Event.isEdition(): Boolean { return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null } + +fun Event.getPresenceContent(): PresenceContent? { + return content.toModel<PresenceContent>() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt new file mode 100644 index 0000000000..82a81f4b64 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.presence + +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +/** + * This interface defines methods for handling user presence information. + */ +interface PresenceService { + /** + * Update the presence status for the current user + * @param presence the new presence state + * @param statusMsg the status message to attach to this state + */ + suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String? = null) + + /** + * Fetch the given user's presence state. + * @param userId the userId whose presence state to get. + */ + suspend fun fetchPresence(userId: String): UserPresence + + // TODO Add live data (of Flow) of the presence of a userId +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt new file mode 100644 index 0000000000..6d9994ef1c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class PresenceEnum(val value: String) { + @Json(name = "online") + ONLINE("online"), + + @Json(name = "offline") + OFFLINE("offline"), + + @Json(name = "unavailable") + UNAVAILABLE("unavailable"); + + companion object { + fun from(s: String): PresenceEnum? = values().find { it.value == s } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt new file mode 100644 index 0000000000..6b33ff07d5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.presence.model + +data class UserPresence( + val lastActiveAgo: Long? = null, + val statusMessage: String? = null, + val isCurrentlyActive: Boolean? = null, + val presence: PresenceEnum = PresenceEnum.OFFLINE +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt index fba3a1dd71..39177a4296 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt @@ -16,12 +16,15 @@ package org.matrix.android.sdk.api.session.room.model +import org.matrix.android.sdk.api.session.presence.model.UserPresence + /** * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content */ data class RoomMemberSummary constructor( val membership: Membership, val userId: String, + val userPresence: UserPresence? = null, val displayName: String? = null, val avatarUrl: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index cae4775e71..10cad026bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room.model import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.sender.SenderInfo @@ -38,6 +39,7 @@ data class RoomSummary( val joinRules: RoomJoinRules? = null, val isDirect: Boolean = false, val directUserId: String? = null, + val directUserPresence: UserPresence? = null, val joinedMembersCount: Int? = 0, val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index aa96ca5e1a..05137f8105 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -35,19 +35,21 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityField import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.query.process import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 17L + const val SESSION_STORE_SCHEMA_VERSION = 18L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -69,6 +71,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) + if (oldVersion <= 17) migrateTo18(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -338,4 +341,27 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("EventInsertEntity") ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java) } + + private fun migrateTo18(realm: DynamicRealm) { + Timber.d("Step 17 -> 18") + realm.schema.create("UserPresenceEntity") + ?.addField(UserPresenceEntityFields.USER_ID, String::class.java) + ?.addPrimaryKey(UserPresenceEntityFields.USER_ID) + ?.setRequired(UserPresenceEntityFields.USER_ID, true) + ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java) + ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java) + ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true) + ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java) + ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java) + ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true) + ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java) + ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java) + + val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return + realm.schema.get("RoomSummaryEntity") + ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity) + + realm.schema.get("RoomMemberSummaryEntity") + ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt index 2365a39567..efd9b68011 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt @@ -18,12 +18,14 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence internal object RoomMemberSummaryMapper { fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary { return RoomMemberSummary( userId = roomMemberSummaryEntity.userId, + userPresence = roomMemberSummaryEntity.userPresenceEntity?.toUserPresence(), avatarUrl = roomMemberSummaryEntity.avatarUrl, displayName = roomMemberSummaryEntity.displayName, membership = roomMemberSummaryEntity.membership diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 0cf431c340..5900ef6b76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import javax.inject.Inject @@ -48,6 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa joinRules = roomSummaryEntity.joinRules, isDirect = roomSummaryEntity.isDirect, directUserId = roomSummaryEntity.directUserId, + directUserPresence = roomSummaryEntity.directUserPresence?.toUserPresence(), latestPreviewableEvent = latestEvent, joinedMembersCount = roomSummaryEntity.joinedMembersCount, invitedMembersCount = roomSummaryEntity.invitedMembersCount, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index 75771ff12c..a8a76d1681 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -21,6 +21,7 @@ import io.realm.annotations.Index import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "", @Index var userId: String = "", @@ -40,6 +41,11 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = membershipStr = value.name } + var userPresenceEntity: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + fun toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 64dc08e827..88b8886936 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", @@ -204,6 +205,11 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var directUserPresence: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + var hasFailedSending: Boolean = false set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 19472e21d9..c090777972 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.annotations.RealmModule +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity /** * Realm module for Session @@ -64,6 +65,7 @@ import io.realm.annotations.RealmModule WellknownIntegrationManagerConfigEntity::class, RoomAccountDataEntity::class, SpaceChildSummaryEntity::class, - SpaceParentSummaryEntity::class + SpaceParentSummaryEntity::class, + UserPresenceEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt new file mode 100644 index 0000000000..5713337ec5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.model.presence + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +internal open class UserPresenceEntity(@PrimaryKey var userId: String = "", + var lastActiveAgo: Long? = null, + var statusMessage: String? = null, + var isCurrentlyActive: Boolean? = null, + var avatarUrl: String? = null, + var displayName: String? = null +) : RealmObject() { + + var presence: PresenceEnum + get() { + return PresenceEnum.valueOf(presenceStr) + } + set(value) { + presenceStr = value.name + } + + private var presenceStr: String = PresenceEnum.UNAVAILABLE.name + + companion object +} + +internal fun UserPresenceEntity.toUserPresence() = + UserPresence( + lastActiveAgo, + statusMessage, + isCurrentlyActive, + presence + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt index a19a9cf725..1ea06b9dfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt @@ -21,6 +21,7 @@ import io.realm.RealmQuery import io.realm.kotlin.where import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> { val query = realm @@ -32,3 +33,13 @@ internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: Strin } return query } + +internal fun RoomMemberSummaryEntity.Companion.updateUserPresence(realm: Realm, userId: String, userPresenceEntity: UserPresenceEntity) { + realm.where<RoomMemberSummaryEntity>() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId) + .isNull(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`) + .findAll() + .map { + it.userPresenceEntity = userPresenceEntity + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 5294f849af..d1b05a4932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -23,6 +23,7 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> { val query = realm.where<RoomSummaryEntity>() @@ -67,3 +68,11 @@ internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String): .findAll() .isNotEmpty() } + +internal fun RoomSummaryEntity.Companion.updateDirectUserPresence(realm: Realm, directUserId: String, userPresenceEntity: UserPresenceEntity) { + RoomSummaryEntity.where(realm) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .equalTo(RoomSummaryEntityFields.DIRECT_USER_ID, directUserId) + .findFirst() + ?.directUserPresence = userPresenceEntity +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt new file mode 100644 index 0000000000..22790b6f4f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields + +internal fun UserPresenceEntity.Companion.where(realm: Realm, userId: String): RealmQuery<UserPresenceEntity> { + return realm + .where<UserPresenceEntity>() + .equalTo(UserPresenceEntityFields.USER_ID, userId) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 7260517ec5..6a6bce5ce2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -46,6 +46,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -129,6 +130,7 @@ internal class DefaultSession @Inject constructor( private val callSignalingService: Lazy<CallSignalingService>, private val spaceService: Lazy<SpaceService>, private val openIdService: Lazy<OpenIdService>, + private val presenceService: Lazy<PresenceService>, @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient> ) : Session, @@ -147,6 +149,7 @@ internal class DefaultSession @Inject constructor( SecureStorageService by secureStorageService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), ProfileService by profileService.get(), + PresenceService by presenceService.get(), AccountService by accountService.get() { override val sharedSecretStorageService: SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 4c2498ab8b..bc8a707530 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule import org.matrix.android.sdk.internal.session.media.MediaModule import org.matrix.android.sdk.internal.session.openid.OpenIdModule +import org.matrix.android.sdk.internal.session.presence.di.PresenceModule import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker import org.matrix.android.sdk.internal.session.pushers.PushersModule @@ -96,6 +97,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule SearchModule::class, ThirdPartyModule::class, SpaceModule::class, + PresenceModule::class, RequestModule::class ] ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt new file mode 100644 index 0000000000..53d0d5e963 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence + +import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path + +internal interface PresenceAPI { + + /** + * Set the presence status of the current user + * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-presence-userid-status + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun setPresence(@Path("userId") userId: String, + @Body body: SetPresenceBody) + + /** + * Get the given user's presence state. + * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-presence-userid-status + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun getPresence(@Path("userId") userId: String): GetPresenceResponse +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt new file mode 100644 index 0000000000..6b2ee76046 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.di + +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.service.DefaultPresenceService +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultGetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultSetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import retrofit2.Retrofit + +@Module +internal abstract class PresenceModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesPresenceAPI(retrofit: Retrofit): PresenceAPI { + return retrofit.create(PresenceAPI::class.java) + } + } + + @Binds + abstract fun bindPresenceService(service: DefaultPresenceService): PresenceService + + @Binds + abstract fun bindSetPresenceTask(task: DefaultSetPresenceTask): SetPresenceTask + + @Binds + abstract fun bindGetPresenceTask(task: DefaultGetPresenceTask): GetPresenceTask +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt new file mode 100644 index 0000000000..a7552f7b02 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +@JsonClass(generateAdapter = true) +data class GetPresenceResponse( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "last_active_ago") + val lastActiveAgo: Long? = null, + @Json(name = "status_msg") + val message: String? = null, + @Json(name = "currently_active") + val isCurrentlyActive: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt new file mode 100644 index 0000000000..45e0fcf06e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +/** + * Class representing the EventType.PRESENCE event content + */ +@JsonClass(generateAdapter = true) +data class PresenceContent( + /** + * Required. The presence state for this user. One of: ["online", "offline", "unavailable"] + */ + @Json(name = "presence") val presence: PresenceEnum, + /** + * The last time since this used performed some action, in milliseconds. + */ + @Json(name = "last_active_ago") val lastActiveAgo: Long? = null, + /** + * An optional description to accompany the presence. + */ + @Json(name = "status_msg") val statusMessage: String? = null, + /** + * Whether the user is currently active + */ + @Json(name = "currently_active") val isCurrentlyActive: Boolean = false, + /** + * The current avatar URL for this user, if any. + */ + @Json(name = "avatar_url") val avatarUrl: String? = null, + /** + * The current display name for this user, if any. + */ + @Json(name = "displayname") val displayName: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt new file mode 100644 index 0000000000..0c81791ec0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +@JsonClass(generateAdapter = true) +internal data class SetPresenceBody( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "status_msg") + val statusMsg: String? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt new file mode 100644 index 0000000000..1083d5b4c2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.service + +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import javax.inject.Inject + +internal class DefaultPresenceService @Inject constructor( + @UserId private val userId: String, + private val setPresenceTask: SetPresenceTask, + private val getPresenceTask: GetPresenceTask +) : PresenceService { + + override suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String?) { + setPresenceTask.execute(SetPresenceTask.Params(userId, presence, statusMsg)) + } + + override suspend fun fetchPresence(userId: String): UserPresence { + val result = getPresenceTask.execute(GetPresenceTask.Params(userId)) + + return UserPresence( + lastActiveAgo = result.lastActiveAgo, + statusMessage = result.message, + isCurrentlyActive = result.isCurrentlyActive, + presence = result.presence + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt new file mode 100644 index 0000000000..bb628dbab4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class GetPresenceTask : Task<GetPresenceTask.Params, GetPresenceResponse> { + data class Params( + val userId: String + ) +} + +internal class DefaultGetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : GetPresenceTask() { + override suspend fun execute(params: Params): GetPresenceResponse { + return executeRequest(globalErrorReceiver) { + presenceAPI.getPresence(params.userId) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt new file mode 100644 index 0000000000..1b3bdabd30 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class SetPresenceTask : Task<SetPresenceTask.Params, Any> { + data class Params( + val userId: String, + val presence: PresenceEnum, + val statusMsg: String? + ) +} + +internal class DefaultSetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : SetPresenceTask() { + + override suspend fun execute(params: Params): Any { + return executeRequest(globalErrorReceiver) { + val setPresenceBody = SetPresenceBody(params.presence, params.statusMsg) + presenceAPI.setPresence(params.userId, setPresenceBody) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt index f78b5d7992..d6a04de5e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -18,10 +18,11 @@ package org.matrix.android.sdk.internal.session.room.membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal object RoomMemberEntityFactory { - fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity { + fun create(roomId: String, userId: String, roomMember: RoomMemberContent, presence: UserPresenceEntity?): RoomMemberSummaryEntity { val primaryKey = "${roomId}_$userId" return RoomMemberSummaryEntity( primaryKey = primaryKey, @@ -31,6 +32,7 @@ internal object RoomMemberEntityFactory { avatarUrl = roomMember.avatarUrl ).apply { membership = roomMember.membership + userPresenceEntity = presence } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 7528f80cc2..25c124bd6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -20,6 +20,9 @@ import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator @@ -47,7 +50,13 @@ internal class RoomMemberEventHandler @Inject constructor( if (roomMember == null) { return false } - val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) + val roomMemberEntity = RoomMemberEntityFactory.create( + roomId, + userId, + roomMember, + // When an update is happening, insertOrUpdate replace existing values with null if they are not provided, + // but we want to preserve presence record value and not replace it with null + getExistingPresenceState(realm, roomId, userId)) realm.insertOrUpdate(roomMemberEntity) if (roomMember.membership.isActive()) { val userEntity = UserEntityFactory.create(userId, roomMember) @@ -60,7 +69,15 @@ internal class RoomMemberEventHandler @Inject constructor( if (mxId != null && mxId != myUserId) { aggregator?.directChatsToCheck?.put(roomId, mxId) } - return true } + + /** + * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity + * by NULL value. + */ + + private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? { + return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index eb48958afb..64f1cc34f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -30,8 +30,8 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index c0e428ec85..0c917448cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -42,7 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 8de36d0427..47e8f7e3a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultTimelineService @AssistedInject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 8c7401ab47..335f619623 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -33,6 +33,12 @@ import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber @@ -55,7 +61,9 @@ internal class SyncResponseHandler @Inject constructor( private val cryptoService: DefaultCryptoService, private val tokenStore: SyncTokenStore, private val processEventForPushTask: ProcessEventForPushTask, - private val pushRuleService: PushRuleService) { + private val pushRuleService: PushRuleService, + private val presenceSyncHandler: PresenceSyncHandler +) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -118,6 +126,13 @@ internal class SyncResponseHandler @Inject constructor( }.also { Timber.v("Finish handling accountData in $it ms") } + + measureTimeMillis { + Timber.v("Handle Presence") + presenceSyncHandler.handle(realm, syncResponse.presence) + }.also { + Timber.v("Finish handling Presence in $it ms") + } tokenStore.saveToken(realm, syncResponse.nextBatch) } @@ -145,7 +160,8 @@ internal class SyncResponseHandler @Inject constructor( private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) { roomsSyncResponse.invite.keys.forEach { roomId -> sessionListeners.dispatch { session, listener -> - listener.onNewInvitedRoom(session, roomId) } + listener.onNewInvitedRoom(session, roomId) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index cc4ccc2e46..fe44531390 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index cec5689a82..c5ec34176c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt index 2b054e578f..552462e25e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import io.realm.Realm import org.matrix.android.sdk.api.session.initsync.InitSyncStep diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt new file mode 100644 index 0000000000..fe173a35c3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.sync.handler + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.getPresenceContent +import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.updateDirectUserPresence +import org.matrix.android.sdk.internal.database.query.updateUserPresence +import javax.inject.Inject + +internal class PresenceSyncHandler @Inject constructor() { + + fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) { + presenceSyncResponse?.events + ?.filter { event -> event.type == EventType.PRESENCE } + ?.forEach { event -> + val content = event.getPresenceContent() ?: return@forEach + val userId = event.senderId ?: return@forEach + val userPresenceEntity = UserPresenceEntity( + userId = userId, + lastActiveAgo = content.lastActiveAgo, + statusMessage = content.statusMessage, + isCurrentlyActive = content.isCurrentlyActive, + avatarUrl = content.avatarUrl, + displayName = content.displayName + ).also { + it.presence = content.presence + } + + storePresenceToDB(realm, userPresenceEntity) + } + } + + /** + * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly + */ + private fun storePresenceToDB(realm: Realm, userPresenceEntity: UserPresenceEntity) = + realm.copyToRealmOrUpdate(userPresenceEntity)?.apply { + RoomSummaryEntity.updateDirectUserPresence(realm, userPresenceEntity.userId, this) + RoomMemberSummaryEntity.updateUserPresence(realm, userPresenceEntity.userId, this) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index db1100d76c..1e0e87a450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -14,9 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt similarity index 99% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index a9926c70aa..3e38cd7839 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import com.zhuinden.monarchy.Monarchy import io.realm.Realm diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt index fc1a2c3870..025ee329f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.EventType @@ -23,6 +23,8 @@ import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.query.createUnmanaged import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt index 3d0db212c2..b5c8a099d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 52e5b6b58d..8c4af81c99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import io.realm.kotlin.createObject @@ -62,6 +62,9 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent +import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator +import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler import org.matrix.android.sdk.internal.util.computeBestChunkSize import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt index 8997435be4..55b15624bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt index 1433d89143..63db13a5b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.sender.SenderInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index 6ca008c5b1..5e7bde87e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -28,8 +28,8 @@ import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityField import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.session.room.read.FullyReadContent -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler -import org.matrix.android.sdk.internal.session.sync.RoomTagHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler import javax.inject.Inject internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index e5c338d511..ddcac475ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource -import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt index 6eb06722b9..e6451b34ef 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt @@ -23,6 +23,7 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.ui.views.PresenceStateImageView import im.vector.app.core.ui.views.ShieldImageView @EpoxyModelClass(layout = R.layout.item_profile_matrix_item) @@ -31,6 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem<ProfileMatrixItem.Holde open class Holder : VectorEpoxyHolder() { val titleView by bind<TextView>(R.id.matrixItemTitle) val subtitleView by bind<TextView>(R.id.matrixItemSubtitle) + val presenceImageView by bind<PresenceStateImageView>(R.id.matrixItemPresenceImageView) val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar) val avatarDecorationImageView by bind<ShieldImageView>(R.id.matrixItemAvatarDecoration) val editableView by bind<View>(R.id.matrixItemEditable) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt new file mode 100644 index 0000000000..92216cbb38 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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.app.core.epoxy.profiles + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +@EpoxyModelClass(layout = R.layout.item_profile_matrix_item) +abstract class ProfileMatrixItemWithPowerLevelWithPresence : ProfileMatrixItemWithPowerLevel() { + + @EpoxyAttribute var userPresence: UserPresence? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.presenceImageView.render(userPresence = userPresence) + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt new file mode 100644 index 0000000000..301f8afdc9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 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.app.core.ui.views + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import im.vector.app.R +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +/** + * Custom ImageView to dynamically render Presence state in multiple screens + */ +class PresenceStateImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatImageView(context, attrs, defStyleAttr) { + + fun render(showPresence: Boolean = true, userPresence: UserPresence?) { + isVisible = showPresence && userPresence != null + + when (userPresence?.presence) { + PresenceEnum.ONLINE -> { + setImageResource(R.drawable.ic_presence_online) + contentDescription = context.getString(R.string.a11y_presence_online) + } + PresenceEnum.UNAVAILABLE -> { + setImageResource(R.drawable.ic_presence_offline) + contentDescription = context.getString(R.string.a11y_presence_unavailable) + } + PresenceEnum.OFFLINE -> { + setImageResource(R.drawable.ic_presence_offline) + contentDescription = context.getString(R.string.a11y_presence_offline) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 26364a55ec..e9948e6cf4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1427,9 +1427,10 @@ class RoomDetailFragment @Inject constructor( views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN views.roomToolbarTitleView.text = roomSummary.displayName avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView) - renderSubTitle(typingMessage, roomSummary.topic) views.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) + views.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) + views.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt index e4abbeb1ff..fd0ccd17e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt @@ -32,11 +32,13 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.ui.views.PresenceStateImageView import im.vector.app.core.ui.views.ShieldImageView import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass(layout = R.layout.item_room) @@ -53,6 +55,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence @EpoxyAttribute lateinit var lastEventTime: CharSequence @EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null + @EpoxyAttribute var userPresence: UserPresence? = null + @EpoxyAttribute var showPresence: Boolean = false @EpoxyAttribute var izPublic: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var hasUnreadMessage: Boolean = false @@ -83,6 +87,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { renderSelection(holder, showSelected) holder.typingView.setTextOrHide(typingMessage) holder.lastEventView.isInvisible = holder.typingView.isVisible + holder.roomAvatarPresenceImageView.render(showPresence, userPresence) } override fun unbind(holder: Holder) { @@ -117,6 +122,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { val roomAvatarDecorationImageView by bind<ShieldImageView>(R.id.roomAvatarDecorationImageView) val roomAvatarPublicDecorationImageView by bind<ImageView>(R.id.roomAvatarPublicDecorationImageView) val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView) + val roomAvatarPresenceImageView by bind<PresenceStateImageView>(R.id.roomAvatarPresenceImageView) val rootView by bind<ViewGroup>(R.id.itemRoomLayout) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index c06ab33a54..fdb7d3a323 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -124,6 +124,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor // We do not display shield in the room list anymore // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .izPublic(roomSummary.isPublic) + .showPresence(roomSummary.isDirect) + .userPresence(roomSummary.directUserPresence) .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) .typingMessage(typingMessage) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 4b37d038b5..e07746af85 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -219,6 +219,8 @@ class RoomProfileFragment @Inject constructor( avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView) headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel) views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) + headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence) + headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect } } roomProfileController.setData(state) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 8399c9e238..ed0b88c8d3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -17,14 +17,19 @@ package im.vector.app.features.roomprofile.members import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.epoxy.profiles.profileMatrixItem +import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevelWithPresence import im.vector.app.core.extensions.join +import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer +import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.util.MatrixItem @@ -34,6 +39,7 @@ import javax.inject.Inject class RoomMemberListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, private val roomMemberSummaryFilter: RoomMemberSummaryFilter ) : TypedEpoxyController<RoomMemberListViewState>() { @@ -84,17 +90,10 @@ class RoomMemberListController @Inject constructor( buildProfileSection( stringProvider.getString(powerLevelCategory.titleRes) ) + filteredRoomMemberList.join( each = { _, roomMember -> - profileMatrixItem { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) - avatarRenderer(host.avatarRenderer) - userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) - clickListener { - host.callback?.onRoomMemberClicked(roomMember) - } - } + buildPresence(roomMember, powerLevelCategory, host, data, roomMember.userPresence) }, between = { _, roomMemberBefore -> dividerItem { @@ -123,6 +122,33 @@ class RoomMemberListController @Inject constructor( } } + private fun buildPresence(roomMember: RoomMemberSummary, + powerLevelCategory: RoomMemberListCategories, + host: RoomMemberListController, + data: RoomMemberListViewState, + userPresence: UserPresence? + ) { + val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) + + profileMatrixItemWithPowerLevelWithPresence { + id(roomMember.userId) + matrixItem(roomMember.toMatrixItem()) + avatarRenderer(host.avatarRenderer) + userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + clickListener { + host.callback?.onRoomMemberClicked(roomMember) + } + userPresence(userPresence) + powerLevelLabel( + span { + span(powerLabel) { + textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + } + } + ) + } + } + private fun buildThreePidInvites(data: RoomMemberListViewState) { val host = this data.threePidInvites() diff --git a/vector/src/main/res/drawable/ic_presence_offline.xml b/vector/src/main/res/drawable/ic_presence_offline.xml new file mode 100644 index 0000000000..3f0dc251ce --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_offline.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="11.89dp" + android:height="12dp" + android:viewportWidth="11.89" + android:viewportHeight="12" + > + + <group> + + <clip-path + android:pathData="M11.8857 6C11.8857 9.31371 9.225 12 5.94286 12C2.66071 12 0 9.31371 0 6C0 2.68629 2.66071 0 5.94286 0C9.225 0 11.8857 2.68629 11.8857 6Z" + /> + + <path + android:pathData="M0 0V12H11.8857V0" + android:fillColor="?vctr_presence_indicator_offline" + /> + + </group> + +</vector> \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_presence_online.xml b/vector/src/main/res/drawable/ic_presence_online.xml new file mode 100644 index 0000000000..2184f359b2 --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_online.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="11.89dp" + android:height="12dp" + android:viewportWidth="11.89" + android:viewportHeight="12" + > + + <group> + + <clip-path + android:pathData="M11.8857 6C11.8857 9.31371 9.225 12 5.94286 12C2.66071 12 0 9.31371 0 6C0 2.68629 2.66071 0 5.94286 0C9.225 0 11.8857 2.68629 11.8857 6Z" + /> + + <path + android:pathData="M0 0V12H11.8857V0" + android:fillColor="#0DBD8B" + /> + + </group> + +</vector> diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 6c58f40bad..c0ac3170e5 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -45,19 +45,50 @@ <im.vector.app.core.ui.views.ShieldImageView android:id="@+id/roomToolbarDecorationImageView" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="17dp" + android:layout_height="17dp" + android:layout_marginStart="5dp" + android:layout_marginTop="2dp" + app:layout_constraintBottom_toBottomOf="@+id/roomToolbarTitleView" + app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView" + app:layout_constraintTop_toTopOf="@+id/roomToolbarTitleView" /> + + <im.vector.app.core.ui.views.PresenceStateImageView + android:id="@+id/roomToolbarPresenceImageView" + android:layout_width="12dp" + android:layout_height="12dp" + android:background="@drawable/background_circle" + android:padding="2dp" + android:visibility="gone" app:layout_constraintCircle="@+id/roomToolbarAvatarImageView" app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="20dp" - tools:ignore="MissingConstraints" /> + tools:ignore="MissingConstraints" + tools:layout_constraintCircleRadius="8dp" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + <ImageView + android:id="@+id/roomToolbarPublicImageView" + android:layout_width="13dp" + android:layout_height="13dp" + android:background="@drawable/background_circle" + android:contentDescription="@string/a11y_public_room" + android:padding="1dp" + android:src="@drawable/ic_public_room" + android:visibility="gone" + app:layout_constraintCircle="@+id/roomToolbarAvatarImageView" + app:layout_constraintCircleAngle="135" + app:layout_constraintCircleRadius="20dp" + tools:ignore="MissingConstraints" + tools:visibility="visible" /> <TextView android:id="@+id/roomToolbarTitleView" style="@style/Widget.Vector.TextView.HeadlineMedium" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="4dp" android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" @@ -65,8 +96,10 @@ app:layout_constraintBottom_toTopOf="@+id/roomToolbarSubtitleView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" - app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView" + app:layout_constraintStart_toEndOf="@+id/roomToolbarDecorationImageView" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginStart="7dp" tools:text="@sample/rooms.json/data/name" /> <TextView @@ -74,9 +107,8 @@ style="@style/Widget.Vector.TextView.Body" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="7dp" android:layout_marginEnd="8dp" - android:layout_marginBottom="8dp" android:ellipsize="end" android:maxLines="1" android:textColor="?vctr_content_primary" @@ -85,7 +117,8 @@ app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView" app:layout_constraintTop_toBottomOf="@+id/roomToolbarTitleView" - tools:text="@sample/rooms.json/data/topic" /> + tools:text="@sample/rooms.json/data/topic" + tools:visibility="visible" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml index 636113752b..cea2f62968 100644 --- a/vector/src/main/res/layout/item_profile_matrix_item.xml +++ b/vector/src/main/res/layout/item_profile_matrix_item.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- https://tinyurl.com/PresenceListInRooms --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" @@ -28,19 +29,35 @@ <im.vector.app.core.ui.views.ShieldImageView android:id="@+id/matrixItemAvatarDecoration" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="5dp" + android:layout_marginTop="2dp" + app:layout_constraintBottom_toBottomOf="@+id/matrixItemTitle" + app:layout_constraintStart_toEndOf="@+id/matrixItemAvatar" + app:layout_constraintTop_toTopOf="@+id/matrixItemTitle" /> + + <im.vector.app.core.ui.views.PresenceStateImageView + android:id="@+id/matrixItemPresenceImageView" + android:layout_width="12dp" + android:layout_height="12dp" + android:background="@drawable/background_circle" + android:importantForAccessibility="no" + android:padding="2dp" + android:visibility="gone" app:layout_constraintCircle="@+id/matrixItemAvatar" app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="16dp" - tools:ignore="MissingConstraints" /> + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> <TextView android:id="@+id/matrixItemTitle" style="@style/Widget.Vector.TextView.Subtitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="4dp" android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" @@ -48,9 +65,10 @@ app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle" app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel" - app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" + app:layout_constraintStart_toEndOf="@+id/matrixItemAvatarDecoration" app:layout_constraintTop_toTopOf="parent" app:layout_goneMarginEnd="80dp" + app:layout_goneMarginStart="7dp" tools:text="@sample/users.json/data/displayName" /> <TextView @@ -58,7 +76,7 @@ style="@style/Widget.Vector.TextView.Body" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="7dp" android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 601ee14799..e690dd122c 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -86,6 +86,22 @@ tools:ignore="MissingConstraints" tools:visibility="visible" /> + <im.vector.app.core.ui.views.PresenceStateImageView + android:id="@+id/roomAvatarPresenceImageView" + android:layout_width="16dp" + android:layout_height="16dp" + android:background="@drawable/background_circle" + android:importantForAccessibility="no" + android:padding="2dp" + android:visibility="gone" + app:layout_constraintCircle="@id/roomAvatarContainer" + app:layout_constraintCircleAngle="135" + app:layout_constraintCircleRadius="28dp" + tools:ignore="MissingConstraints" + tools:layout_constraintCircleRadius="8dp" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + <!-- Margin bottom does not work, so I use space --> <Space android:id="@+id/roomAvatarBottomSpace" diff --git a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml index e7d930b070..0b3e21a348 100644 --- a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml @@ -30,14 +30,33 @@ app:layout_constraintVertical_chainStyle="spread_inside" tools:src="@sample/user_round_avatars" /> - <im.vector.app.core.ui.views.ShieldImageView - android:id="@+id/memberProfileDecorationImageView" - android:layout_width="48dp" - android:layout_height="48dp" + + <im.vector.app.core.ui.views.PresenceStateImageView + android:id="@+id/memberProfilePresenceImageView" + android:layout_width="26dp" + android:layout_height="26dp" + android:background="@drawable/background_circle" + android:importantForAccessibility="no" + android:padding="3dp" + android:visibility="gone" app:layout_constraintCircle="@+id/memberProfileAvatarView" app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="64dp" - tools:ignore="MissingConstraints" /> + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + <im.vector.app.core.ui.views.ShieldImageView + android:id="@+id/memberProfileDecorationImageView" + android:layout_width="30dp" + android:layout_height="30dp" + android:layout_marginTop="2dp" + android:layout_marginEnd="8dp" + app:layout_constraintBottom_toBottomOf="@+id/memberProfileNameView" + app:layout_constraintEnd_toStartOf="@+id/memberProfileNameView" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/memberProfileNameView" /> <TextView android:id="@+id/memberProfileNameView" @@ -48,7 +67,7 @@ android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/memberProfileIdView" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@+id/memberProfileDecorationImageView" app:layout_constraintTop_toBottomOf="@+id/memberProfileAvatarView" tools:text="@sample/users.json/data/displayName" /> diff --git a/vector/src/main/res/layout/view_stub_room_profile_header.xml b/vector/src/main/res/layout/view_stub_room_profile_header.xml index 7308e8a207..b6fc16e606 100644 --- a/vector/src/main/res/layout/view_stub_room_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_profile_header.xml @@ -20,25 +20,58 @@ app:layout_constraintTop_toTopOf="parent" tools:src="@sample/room_round_avatars" /> - <im.vector.app.core.ui.views.ShieldImageView - android:id="@+id/roomProfileDecorationImageView" - android:layout_width="48dp" - android:layout_height="48dp" + <im.vector.app.core.ui.views.PresenceStateImageView + android:id="@+id/roomProfilePresenceImageView" + android:layout_width="26dp" + android:layout_height="26dp" + android:background="@drawable/background_circle" + android:importantForAccessibility="no" + android:padding="3dp" + android:visibility="gone" app:layout_constraintCircle="@+id/roomProfileAvatarView" app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="64dp" - tools:ignore="MissingConstraints" /> + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + <ImageView + android:id="@+id/roomProfilePublicImageView" + android:layout_width="28dp" + android:layout_height="28dp" + android:background="@drawable/background_circle" + android:contentDescription="@string/a11y_public_room" + android:padding="2dp" + android:src="@drawable/ic_public_room" + android:visibility="gone" + app:layout_constraintCircle="@+id/roomProfileAvatarView" + app:layout_constraintCircleAngle="135" + app:layout_constraintCircleRadius="64dp" + tools:ignore="MissingConstraints" + tools:visibility="visible" /> + + <im.vector.app.core.ui.views.ShieldImageView + android:id="@+id/roomProfileDecorationImageView" + android:layout_width="30dp" + android:layout_height="30dp" + android:layout_marginTop="2dp" + android:layout_marginEnd="8dp" + app:layout_constraintBottom_toBottomOf="@+id/roomProfileNameView" + app:layout_constraintEnd_toStartOf="@+id/roomProfileNameView" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/roomProfileNameView"/> <TextView android:id="@+id/roomProfileNameView" style="@style/Widget.Vector.TextView.Title" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/roomProfileAliasView" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@+id/roomProfileDecorationImageView" app:layout_constraintTop_toBottomOf="@+id/roomProfileAvatarView" tools:text="@sample/rooms.json/data/name" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a61ba52a79..7fa4918266 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3406,6 +3406,9 @@ <string name="a11y_view_read_receipts">View read receipts</string> <string name="a11y_public_room">Public room</string> <string name="a11y_public_space">Public space</string> + <string name="a11y_presence_online">Online</string> + <string name="a11y_presence_offline">Offline</string> + <string name="a11y_presence_unavailable">Unavailable</string> <string name="dev_tools_menu_name">Dev Tools</string> <string name="dev_tools_explore_room_state">Explore Room State</string>