From 385720b89da9bae501094821a9ac156e5330e577 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 15 Jun 2022 11:46:57 +0200 Subject: [PATCH 001/133] Replaces flatten parents with direct parent name in RoomSummary --- .../sdk/api/session/room/RoomService.kt | 3 -- .../sdk/api/session/room/model/RoomSummary.kt | 4 +-- .../database/RealmSessionStoreMigration.kt | 2 ++ .../database/mapper/RoomSummaryMapper.kt | 1 + .../database/migration/MigrateSessionTo030.kt | 30 +++++++++++++++++++ .../database/model/RoomSummaryEntity.kt | 5 ++++ .../session/room/DefaultRoomService.kt | 3 +- .../room/summary/RoomSummaryDataSource.kt | 10 +------ .../room/summary/RoomSummaryUpdater.kt | 14 ++++----- .../room/list/RoomListSectionBuilderGroup.kt | 2 +- .../room/list/RoomListSectionBuilderSpace.kt | 2 +- .../home/room/list/RoomSummaryItemFactory.kt | 6 ++-- 12 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 5dfb8961e3..cb4e7d9365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -231,14 +231,11 @@ interface RoomService { * @param queryParams The filter to use * @param pagedListConfig The paged list configuration (page size, initial load, prefetch distance...) * @param sortOrder defines how to sort the results - * @param getFlattenParents When true, the list of known parents and grand parents summaries will be resolved. - * This can have significant impact on performance, better be used only on manageable list (filtered by displayName, ..). */ fun getFilteredPagedRoomSummariesLive( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config = defaultPagedListConfig, sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY, - getFlattenParents: Boolean = false, ): UpdatableLivePageResult /** 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 1ab23b7a11..04c90cdc83 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 @@ -164,9 +164,9 @@ data class RoomSummary( */ val spaceChildren: List? = null, /** - * List of all the space parents. Will be empty by default, you have to explicitly request it. + * The name of the room's direct space parent if any */ - val flattenParents: List = emptyList(), + val directParentName: String? = null, /** * List of all the space parent Ids. */ 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 592461f927..4021efb4d5 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 @@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -95,5 +96,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 27) MigrateSessionTo027(realm).perform() if (oldVersion < 28) MigrateSessionTo028(realm).perform() if (oldVersion < 29) MigrateSessionTo029(realm).perform() + if (oldVersion < 30) MigrateSessionTo030(realm).perform() } } 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 735cfe411c..2697a44395 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 @@ -106,6 +106,7 @@ internal class RoomSummaryMapper @Inject constructor( worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC ) }, + directParentName = roomSummaryEntity.directParentName, flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList(), roomEncryptionAlgorithm = when (val alg = roomSummaryEntity.e2eAlgorithm) { // I should probably use #hasEncryptorClassForAlgorithm but it says it supports diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt new file mode 100644 index 0000000000..9a28cf34c7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 30) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.DIRECT_PARENT_NAME, String::class.java) + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAME, "") } // TODO: make this get the direct parent name + } +} 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 cd755590be..1bdc2d92d3 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 @@ -235,6 +235,11 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var directParentName: String? = null + set(value) { + if (value != field) field = value + } + var flattenParentIds: String? = null set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 5e6d052443..646e9fb80c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -140,9 +140,8 @@ internal class DefaultRoomService @Inject constructor( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder, - getFlattenParents: Boolean ): UpdatableLivePageResult { - return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents) + return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder) } override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index cb7dc270e8..0895df3fd0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -200,14 +200,13 @@ internal class RoomSummaryDataSource @Inject constructor( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder, - getFlattenedParents: Boolean = false ): UpdatableLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams).process(sortOrder) } val dataSourceFactory = realmDataSourceFactory.map { roomSummaryMapper.map(it) - }.map { if (getFlattenedParents) it.getWithParents() else it } + } val boundaries = MutableLiveData(ResultBoundaries()) @@ -246,13 +245,6 @@ internal class RoomSummaryDataSource @Inject constructor( } } - private fun RoomSummary.getWithParents(): RoomSummary { - val parents = flattenParentIds.mapNotNull { parentId -> - getRoomSummary(parentId) - } - return copy(flattenParents = parents) - } - fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData { val liveRooms = monarchy.findAllManagedWithChanges { roomSummariesQuery(it, queryParams) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index e4afe7aa49..6659f7f8d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -366,24 +366,20 @@ internal class RoomSummaryUpdater @Inject constructor( .forEach { entry -> val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst() if (parent != null) { -// Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}") -// Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}") val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId) -// Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}") + entry.value.forEach { child -> RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum -> + childSum.directParentName = parent.displayName() -// Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}") -// var allParents = childSum.flattenParentIds ?: "" - if (childSum.flattenParentIds == null) childSum.flattenParentIds = "" + if (childSum.flattenParentIds == null) { + childSum.flattenParentIds = "" + } flattenParentsIds.forEach { if (childSum.flattenParentIds?.contains(it) != true) { childSum.flattenParentIds += "|$it" } } -// childSum.flattenParentIds = "$allParents|" - -// Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}") } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index a8a30349c7..8f7ab46191 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -71,7 +71,7 @@ class RoomListSectionBuilderGroup( }, { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm, getFlattenParents = true) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 47a1df3ed0..6b426a3c50 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -332,7 +332,7 @@ class RoomListSectionBuilderSpace( }, { queryParams -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() 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 f50cec5149..8941547ac5 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 @@ -206,10 +206,10 @@ class RoomSummaryItemFactory @Inject constructor( .itemClickListener { onClick?.invoke(roomSummary) } private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { - val userId = roomSummary.directUserId - val spaceName = roomSummary.flattenParents.lastOrNull()?.name + val userId = roomSummary.directParentName + val directParent = roomSummary.directParentName val canonicalAlias = roomSummary.canonicalAlias - return (userId ?: spaceName ?: canonicalAlias).orEmpty() + return (userId ?: directParent ?: canonicalAlias).orEmpty() } } From d40c2f8c78da8fdc63ed9ccc2d6693615d574ad7 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 15 Jun 2022 14:42:08 +0200 Subject: [PATCH 002/133] Fixes migration error --- .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4021efb4d5..6a8589bc5e 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 @@ -62,7 +62,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 29L + val schemaVersion = 30L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") From 7f3325e8dfae1b03df44be3e066ef2acae7abce1 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 15 Jun 2022 14:47:26 +0200 Subject: [PATCH 003/133] Fixes wrong user id in search room summary item --- .../sdk/internal/database/migration/MigrateSessionTo030.kt | 2 +- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt index 9a28cf34c7..87c367e0c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt @@ -25,6 +25,6 @@ internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 3 override fun doMigrate(realm: DynamicRealm) { realm.schema.get("RoomSummaryEntity") ?.addField(RoomSummaryEntityFields.DIRECT_PARENT_NAME, String::class.java) - ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAME, "") } // TODO: make this get the direct parent name + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAME, "") } } } 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 8941547ac5..6d330952ed 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 @@ -206,7 +206,7 @@ class RoomSummaryItemFactory @Inject constructor( .itemClickListener { onClick?.invoke(roomSummary) } private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { - val userId = roomSummary.directParentName + val userId = roomSummary.directUserId val directParent = roomSummary.directParentName val canonicalAlias = roomSummary.canonicalAlias From b5fc0b502c84fc0fe1f50bc9a76012b89bceb5cc Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 15 Jun 2022 15:03:28 +0200 Subject: [PATCH 004/133] Adds changelog file --- changelog.d/6314.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6314.misc diff --git a/changelog.d/6314.misc b/changelog.d/6314.misc new file mode 100644 index 0000000000..865d965d33 --- /dev/null +++ b/changelog.d/6314.misc @@ -0,0 +1 @@ +Improves performance on search screen by replacing flattenParents with directParentName in RoomSummary From 9766b625394de852c16d2aee6fd9c7bb338f0b72 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 15 Jun 2022 16:00:00 +0200 Subject: [PATCH 005/133] Fixes lint error --- .../matrix/android/sdk/api/session/room/model/RoomSummary.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 04c90cdc83..60be5d3eb6 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 @@ -164,7 +164,7 @@ data class RoomSummary( */ val spaceChildren: List? = null, /** - * The name of the room's direct space parent if any + * The name of the room's direct space parent if any. */ val directParentName: String? = null, /** From 86b888c336e6b722926b053fecef14e96ef27b2e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 28 Jun 2022 12:17:18 +0200 Subject: [PATCH 006/133] Display specific message when verif code malformed --- .../qrcode/DefaultQrCodeVerificationTransaction.kt | 2 +- .../conclusion/VerificationConclusionController.kt | 8 ++++++++ .../conclusion/VerificationConclusionViewModel.kt | 7 +++++-- vector/src/main/res/values/strings.xml | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 690ac12268..5b1a4752f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -84,7 +84,7 @@ internal class DefaultQrCodeVerificationTransaction( // Perform some checks if (otherQrCodeData.transactionId != transactionId) { Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId") - cancel(CancelCode.QrCodeInvalid) + cancel(CancelCode.UnknownTransaction) return } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt index 9c5829eb8e..7bd0e393eb 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt @@ -86,6 +86,14 @@ class VerificationConclusionController @Inject constructor( bottomGotIt() } + ConclusionState.INVALID_QR_CODE -> { + bottomSheetVerificationNoticeItem { + id("invalid_qr") + notice(host.stringProvider.getString(R.string.verify_invalid_qr_notice).toEpoxyCharSequence()) + } + + bottomGotIt() + } ConclusionState.CANCELLED -> { bottomSheetVerificationNoticeItem { id("notice_cancelled") diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index aff2d807ac..8883ffb94e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -32,7 +32,8 @@ data class VerificationConclusionViewState( enum class ConclusionState { SUCCESS, WARNING, - CANCELLED + CANCELLED, + INVALID_QR_CODE } class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) : @@ -44,7 +45,9 @@ class VerificationConclusionViewModel(initialState: VerificationConclusionViewSt val args = viewModelContext.args() return when (safeValueOf(args.cancelReason)) { - CancelCode.QrCodeInvalid, + CancelCode.QrCodeInvalid -> { + VerificationConclusionViewState(ConclusionState.INVALID_QR_CODE, args.isMe) + } CancelCode.MismatchedUser, CancelCode.MismatchedSas, CancelCode.MismatchedCommitment, diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0e959a9564..70f9cf626b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2361,6 +2361,7 @@ Verification has been cancelled. You can start verification again. + This QR code looks malformed. Please try to verify with another method. Verification Cancelled Recovery Passphrase From c0a2b39f6da2cbda0195b69054b8fe28cb0ae167 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 28 Jun 2022 12:24:14 +0200 Subject: [PATCH 007/133] add change log --- changelog.d/6395.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6395.bugfix diff --git a/changelog.d/6395.bugfix b/changelog.d/6395.bugfix new file mode 100644 index 0000000000..ebc22dc41a --- /dev/null +++ b/changelog.d/6395.bugfix @@ -0,0 +1 @@ +Display specific message when verification QR code is malformed From cb400b660da1b8cbe11c1baeade57d0d2730ca61 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 29 Jun 2022 17:07:37 +0200 Subject: [PATCH 008/133] Fixes post merge error --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo032.kt | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt 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 665567bf2a..9be1717f32 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 @@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -63,7 +64,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 31L + val schemaVersion = 32L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -99,5 +100,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 29) MigrateSessionTo029(realm).perform() if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 31) MigrateSessionTo031(realm).perform() + if (oldVersion < 32) MigrateSessionTo032(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt new file mode 100644 index 0000000000..ff8c4cf001 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo032(realm: DynamicRealm) : RealmMigrator(realm, 32) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } + } +} From 03da067e4468e8dd0df76ba392ae2eb6f31996ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Fri, 17 Jun 2022 11:42:43 +0200 Subject: [PATCH 009/133] fixed issues with reporting sync state events from different threads --- changelog.d/6341.bugfix | 1 + .../sdk/api/session/sync/SyncService.kt | 4 +-- .../session/sync/DefaultSyncService.kt | 6 +--- .../session/sync/SyncRequestStateTracker.kt | 29 +++++++++++++------ .../java/im/vector/app/AppStateHandler.kt | 4 +-- .../main/java/im/vector/app/AutoRageShaker.kt | 4 +-- .../AnalyticsAccountDataViewModel.kt | 3 +- .../features/home/HomeActivityViewModel.kt | 4 +-- .../app/features/home/HomeDetailViewModel.kt | 3 +- .../home/room/detail/TimelineViewModel.kt | 4 +-- 10 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 changelog.d/6341.bugfix diff --git a/changelog.d/6341.bugfix b/changelog.d/6341.bugfix new file mode 100644 index 0000000000..6866d8c89d --- /dev/null +++ b/changelog.d/6341.bugfix @@ -0,0 +1 @@ +Fixed issues with reporting sync state events from different threads diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt index 5b2bf651af..71f7ab8494 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt @@ -60,9 +60,9 @@ interface SyncService { fun getSyncStateLive(): LiveData /** - * Get the [SyncRequestState] as a LiveData. + * Get the [SyncRequestState] as a SharedFlow. */ - fun getSyncRequestStateLive(): LiveData + fun getSyncRequestStateFlow(): SharedFlow /** * This method returns a flow of SyncResponse. New value will be pushed through the sync thread. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt index 37869b88f9..691dd7b20d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.internal.session.sync -import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncService import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.WorkManagerProvider @@ -75,9 +73,7 @@ internal class DefaultSyncService @Inject constructor( override fun getSyncState() = getSyncThread().currentState() - override fun getSyncRequestStateLive(): LiveData { - return syncRequestStateTracker.syncRequestState - } + override fun getSyncRequestStateFlow() = syncRequestStateTracker.syncRequestState override fun hasAlreadySynced(): Boolean { return syncTokenStore.getLastToken() != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt index bcc5fcf9ab..03ce8cb3f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt @@ -16,23 +16,26 @@ package org.matrix.android.sdk.internal.session.sync -import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject @SessionScope -internal class SyncRequestStateTracker @Inject constructor() : - ProgressReporter { +internal class SyncRequestStateTracker @Inject constructor( + private val coroutineScope: CoroutineScope +) : ProgressReporter { - val syncRequestState = MutableLiveData() + val syncRequestState = MutableSharedFlow() private var rootTask: TaskInfo? = null // Only to be used for incremental sync fun setSyncRequestState(newSyncRequestState: SyncRequestState.IncrementalSyncRequestState) { - syncRequestState.postValue(newSyncRequestState) + emitSyncState(newSyncRequestState) } /** @@ -42,7 +45,9 @@ internal class SyncRequestStateTracker @Inject constructor() : initialSyncStep: InitialSyncStep, totalProgress: Int ) { - endAll() + if (rootTask != null) { + endAll() + } rootTask = TaskInfo(initialSyncStep, totalProgress, null, 1F) reportProgress(0F) } @@ -71,7 +76,7 @@ internal class SyncRequestStateTracker @Inject constructor() : // Update the progress of the leaf and all its parents leaf.setProgress(progress) // Then update the live data using leaf wording and root progress - syncRequestState.postValue(SyncRequestState.InitialSyncProgressing(leaf.initialSyncStep, root.currentProgress.toInt())) + emitSyncState(SyncRequestState.InitialSyncProgressing(leaf.initialSyncStep, root.currentProgress.toInt())) } } } @@ -86,13 +91,19 @@ internal class SyncRequestStateTracker @Inject constructor() : // And close it endedTask.parent.child = null } else { - syncRequestState.postValue(SyncRequestState.Idle) + emitSyncState(SyncRequestState.Idle) } } } fun endAll() { rootTask = null - syncRequestState.postValue(SyncRequestState.Idle) + emitSyncState(SyncRequestState.Idle) + } + + private fun emitSyncState(state: SyncRequestState) { + coroutineScope.launch { + syncRequestState.emit(state) + } } } diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index d366927d6d..9bd1b3234a 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -18,7 +18,6 @@ package im.vector.app import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.asFlow import arrow.core.Option import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource @@ -147,8 +146,7 @@ class AppStateHandler @Inject constructor( } private fun observeSyncStatus(session: Session) { - session.syncService().getSyncRequestStateLive() - .asFlow() + session.syncService().getSyncRequestStateFlow() .filterIsInstance() .map { session.spaceService().getRootSpaceSummaries().size } .distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index dc561aa821..bb3c8bcf05 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -17,7 +17,6 @@ package im.vector.app import android.content.SharedPreferences -import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.ReportType @@ -261,8 +260,7 @@ class AutoRageShaker @Inject constructor( this.currentActiveSessionId = sessionId hasSynced = session.syncService().hasAlreadySynced() - session.syncService().getSyncRequestStateLive() - .asFlow() + session.syncService().getSyncRequestStateFlow() .onEach { hasSynced = it !is SyncRequestState.InitialSyncProgressing } diff --git a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt index 05358decc9..28929127e1 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.analytics.accountdata -import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -66,7 +65,7 @@ class AnalyticsAccountDataViewModel @AssistedInject constructor( private fun observeInitSync() { combine( - session.syncService().getSyncRequestStateLive().asFlow(), + session.syncService().getSyncRequestStateFlow(), analytics.getUserConsent(), analytics.getAnalyticsId() ) { status, userConsent, analyticsId -> diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index b45e6fbcb0..e67d483983 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -218,8 +217,7 @@ class HomeActivityViewModel @AssistedInject constructor( private fun observeInitialSync() { val session = activeSessionHolder.getSafeActiveSession() ?: return - session.syncService().getSyncRequestStateLive() - .asFlow() + session.syncService().getSyncRequestStateFlow() .onEach { status -> when (status) { is SyncRequestState.Idle -> { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 485d1e33ec..8c3bf0f936 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -199,8 +199,7 @@ class HomeDetailViewModel @AssistedInject constructor( copy(syncState = syncState) } - session.syncService().getSyncRequestStateLive() - .asFlow() + session.syncService().getSyncRequestStateFlow() .filterIsInstance() .setOnEach { copy(incrementalSyncRequestState = it) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 07b20b4914..05b83d5632 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -1130,8 +1129,7 @@ class TimelineViewModel @AssistedInject constructor( copy(syncState = syncState) } - session.syncService().getSyncRequestStateLive() - .asFlow() + session.syncService().getSyncRequestStateFlow() .filterIsInstance() .setOnEach { copy(incrementalSyncRequestState = it) From ed3a201f0ba5d78906c04d87bbf4bcc284efd45f Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 1 Jul 2022 13:34:55 +0100 Subject: [PATCH 010/133] Changes directParentName to a list --- .../android/sdk/api/session/room/model/RoomSummary.kt | 4 ++-- .../sdk/internal/database/mapper/RoomSummaryMapper.kt | 2 +- .../sdk/internal/database/model/RoomSummaryEntity.kt | 5 +---- .../internal/session/room/summary/RoomSummaryUpdater.kt | 8 +------- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) 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 60be5d3eb6..ff4977491f 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 @@ -164,9 +164,9 @@ data class RoomSummary( */ val spaceChildren: List? = null, /** - * The name of the room's direct space parent if any. + * The names of the room's direct space parents if any. */ - val directParentName: String? = null, + val directParentNames: List = emptyList(), /** * List of all the space parent Ids. */ 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 2697a44395..72b0f7a043 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 @@ -106,7 +106,7 @@ internal class RoomSummaryMapper @Inject constructor( worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC ) }, - directParentName = roomSummaryEntity.directParentName, + directParentNames = roomSummaryEntity.directParentNames.toList(), flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList(), roomEncryptionAlgorithm = when (val alg = roomSummaryEntity.e2eAlgorithm) { // I should probably use #hasEncryptorClassForAlgorithm but it says it supports 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 1bdc2d92d3..47764f0c9a 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 @@ -235,10 +235,7 @@ internal open class RoomSummaryEntity( if (value != field) field = value } - var directParentName: String? = null - set(value) { - if (value != field) field = value - } + var directParentNames: RealmList = RealmList() var flattenParentIds: String? = null set(value) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 6659f7f8d9..66e2185dae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -351,15 +351,9 @@ internal class RoomSummaryUpdater @Inject constructor( } val acyclicGraph = graph.withoutEdges(backEdges) -// Timber.v("## SPACES: acyclicGraph $acyclicGraph") val flattenSpaceParents = acyclicGraph.flattenDestination().map { it.key.name to it.value.map { it.name } }.toMap() -// Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") { -// it.first + ": [" + it.second.joinToString(",") + "]" -// }}") - -// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}") lookupMap.entries .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN } @@ -370,7 +364,7 @@ internal class RoomSummaryUpdater @Inject constructor( entry.value.forEach { child -> RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum -> - childSum.directParentName = parent.displayName() + childSum.directParentNames.add(parent.displayName()) if (childSum.flattenParentIds == null) { childSum.flattenParentIds = "" 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 6d330952ed..f2042e216e 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 @@ -207,7 +207,7 @@ class RoomSummaryItemFactory @Inject constructor( private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { val userId = roomSummary.directUserId - val directParent = roomSummary.directParentName + val directParent = roomSummary.directParentNames.lastOrNull() val canonicalAlias = roomSummary.canonicalAlias return (userId ?: directParent ?: canonicalAlias).orEmpty() From e003bc973aa6ed3c8c1689d2059de67e30341b43 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 4 Jul 2022 09:37:36 +0100 Subject: [PATCH 011/133] Adds clearing to directParentNames --- .../sdk/internal/session/room/summary/RoomSummaryUpdater.kt | 1 + .../app/features/home/room/list/RoomSummaryItemFactory.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 66e2185dae..4a89d97b0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -224,6 +224,7 @@ internal class RoomSummaryUpdater @Inject constructor( .sort(RoomSummaryEntityFields.ROOM_ID) .findAll().map { it.flattenParentIds = null + it.directParentNames.clear() it to emptyList().toMutableSet() } .toMap() 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 f2042e216e..79c6fe3209 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 @@ -207,7 +207,7 @@ class RoomSummaryItemFactory @Inject constructor( private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { val userId = roomSummary.directUserId - val directParent = roomSummary.directParentNames.lastOrNull() + val directParent = roomSummary.directParentNames.takeIf { it.isNotEmpty() }?.joinToString() val canonicalAlias = roomSummary.canonicalAlias return (userId ?: directParent ?: canonicalAlias).orEmpty() From 98d195c010578d48332a29963375032ac00af084 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 5 Jul 2022 09:35:05 +0100 Subject: [PATCH 012/133] Changes migration to be realm list field --- .../sdk/internal/database/migration/MigrateSessionTo032.kt | 2 +- .../android/sdk/internal/database/model/RoomSummaryEntity.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt index ff8c4cf001..2a3e497f21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt @@ -24,7 +24,7 @@ internal class MigrateSessionTo032(realm: DynamicRealm) : RealmMigrator(realm, 3 override fun doMigrate(realm: DynamicRealm) { realm.schema.get("RoomSummaryEntity") - ?.addField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } } } 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 47764f0c9a..2cdde9884c 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 @@ -34,7 +34,8 @@ internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", var roomType: String? = null, var parents: RealmList = RealmList(), - var children: RealmList = RealmList() + var children: RealmList = RealmList(), + var directParentNames: RealmList = RealmList(), ) : RealmObject() { private var displayName: String? = "" @@ -235,8 +236,6 @@ internal open class RoomSummaryEntity( if (value != field) field = value } - var directParentNames: RealmList = RealmList() - var flattenParentIds: String? = null set(value) { if (value != field) field = value From c6728dde3886ef5f66172fdb51dfa281ce499575 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 6 Jul 2022 13:36:17 +0100 Subject: [PATCH 013/133] Changes string format of space parents to maintain parity with web --- .../features/home/room/list/RoomSummaryItemFactory.kt | 11 ++++++++++- vector/src/main/res/values/strings.xml | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) 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 79c6fe3209..60ee1eda83 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 @@ -207,9 +207,18 @@ class RoomSummaryItemFactory @Inject constructor( private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { val userId = roomSummary.directUserId - val directParent = roomSummary.directParentNames.takeIf { it.isNotEmpty() }?.joinToString() + val directParent = joinParentNames(roomSummary) val canonicalAlias = roomSummary.canonicalAlias return (userId ?: directParent ?: canonicalAlias).orEmpty() } + + private fun joinParentNames(roomSummary: RoomSummary) = with(roomSummary) { + when (directParentNames.size) { + 0 -> null + 1 -> directParentNames.first() + 2 -> stringProvider.getString(R.string.search_space_two_parents, directParentNames[0], directParentNames[1]) + else -> stringProvider.getString(R.string.search_space_multiple_parents, directParentNames.first(), directParentNames.size - 1) + } + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 933f3f0602..f0597405d8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -762,6 +762,8 @@ Filter room members Filter banned users No results + %1$s and %2$s + %1$s and %2$d others All messages From 051f925f0ebaf9eb615ed37a8e38ad826921d56d Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 7 Jul 2022 13:03:58 +0100 Subject: [PATCH 014/133] Adds MigrateSessionTo032Test --- .../migration/MigrateSessionTo032Test.kt | 36 +++++++++++++++ .../sdk/test/fakes/FakeDynamicRealm.kt | 30 +++++++++++++ .../sdk/test/fakes/FakeDynamicRealmObject.kt | 33 ++++++++++++++ .../sdk/test/fakes/FakeRealmObjectSchema.kt | 45 +++++++++++++++++++ .../android/sdk/test/fakes/FakeRealmSchema.kt | 36 +++++++++++++++ 5 files changed, 180 insertions(+) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt new file mode 100644 index 0000000000..f66ccd9cf2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.internal.database.migration + +import org.junit.Test +import org.matrix.android.sdk.test.fakes.FakeDynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields + +internal class MigrateSessionTo032Test { + + private val fakeDynamicRealm = FakeDynamicRealm() + private val migrator = MigrateSessionTo032(fakeDynamicRealm.instance) + + @Test + fun `when doMigrate, then directParentNames added`() { + migrator.doMigrate(fakeDynamicRealm.instance) + + fakeDynamicRealm.fakeRealmSchema.withObjectSchema("RoomSummaryEntity") + .verifyListFieldAdded(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + .verifyStringTransformation(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt new file mode 100644 index 0000000000..939b3be0c1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.realm.DynamicRealm + +class FakeDynamicRealm( + val fakeRealmSchema: FakeRealmSchema = FakeRealmSchema() +) { + + val instance: DynamicRealm = mockk { + every { schema } returns fakeRealmSchema.instance + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt new file mode 100644 index 0000000000..1e0e1808b0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes + +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.verify +import io.realm.DynamicRealmObject + +class FakeDynamicRealmObject { + + val instance: DynamicRealmObject = mockk { + justRun { setString(any(), any()) } + } + + fun verifySetString(fieldName: String, value: String) { + verify { instance.setString(fieldName, value) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt new file mode 100644 index 0000000000..91f05b9604 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import io.realm.RealmObjectSchema +import io.realm.RealmObjectSchema.Function + +class FakeRealmObjectSchema( + private val fakeDynamicRealmObject: FakeDynamicRealmObject = FakeDynamicRealmObject() +) { + + val instance: RealmObjectSchema = mockk { + every { addRealmListField(any(), any>()) } returns this + every { transform(any()) } returns this + } + + fun verifyListFieldAdded(fieldName: String, type: Class<*>) = apply { + verify { instance.addRealmListField(fieldName, type) } + } + + fun verifyStringTransformation(fieldName: String, transformedInto: String) = apply { + val transformationSlot = slot() + verify { instance.transform(capture(transformationSlot)) } + transformationSlot.captured.apply(fakeDynamicRealmObject.instance) + fakeDynamicRealmObject.verifySetString(fieldName, transformedInto) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt new file mode 100644 index 0000000000..519654b883 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.realm.RealmSchema + +class FakeRealmSchema( + private val fakeRealmObjectSchema: FakeRealmObjectSchema = FakeRealmObjectSchema() +) { + + val instance: RealmSchema = mockk { + every { this@mockk.get(any()) } returns fakeRealmObjectSchema.instance + } + + fun withObjectSchema(className: String): FakeRealmObjectSchema { + verify { instance.get(className) } + return fakeRealmObjectSchema + } +} From cb38dacbb0263e01f9539c50c22409293e2c7119 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Fri, 15 Jul 2022 00:29:33 +0000 Subject: [PATCH 015/133] Update Gradle Wrapper from 7.4.2 to 7.5. Signed-off-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 6 ++++++ gradlew.bat | 14 ++++++++------ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10197 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1e0c8dc42..ef80eb5051 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..a69d9cb6c2 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f938..53a6b238d4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 13b9b5b5d64f64fc26031cd98549012421934514 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 14:47:09 +0200 Subject: [PATCH 016/133] Changes string into plural --- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 6 +++--- vector/src/main/res/values/strings.xml | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) 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 60ee1eda83..a78c3d2fdc 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 @@ -214,11 +214,11 @@ class RoomSummaryItemFactory @Inject constructor( } private fun joinParentNames(roomSummary: RoomSummary) = with(roomSummary) { - when (directParentNames.size) { + when (val size = directParentNames.size) { 0 -> null 1 -> directParentNames.first() - 2 -> stringProvider.getString(R.string.search_space_two_parents, directParentNames[0], directParentNames[1]) - else -> stringProvider.getString(R.string.search_space_multiple_parents, directParentNames.first(), directParentNames.size - 1) + 2 -> stringProvider.getQuantityString(R.plurals.search_space_parents, 1, directParentNames[0], directParentNames[1]) + else -> stringProvider.getQuantityString(R.plurals.search_space_parents, size - 1, directParentNames[0], directParentNames.size - 1) } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f0597405d8..533fbe5bcd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -762,8 +762,10 @@ Filter room members Filter banned users No results - %1$s and %2$s - %1$s and %2$d others + + %1$s and %2$s + %1$s and %2$d others + All messages From 1ae59656325ded1274b121c1c72630e1e4b39bfb Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 14:48:10 +0200 Subject: [PATCH 017/133] Fixes import order --- .../sdk/internal/database/migration/MigrateSessionTo032Test.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt index f66ccd9cf2..195b0ae1e3 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt @@ -17,8 +17,8 @@ package org.matrix.android.sdk.internal.database.migration import org.junit.Test -import org.matrix.android.sdk.test.fakes.FakeDynamicRealm import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeDynamicRealm internal class MigrateSessionTo032Test { From 7d29fbc68c1179b6901f77a9f13499b92c32320a Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 15:06:25 +0200 Subject: [PATCH 018/133] Fixes post merge errors --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo033.kt | 30 +++++++++++++++++++ ...o032Test.kt => MigrateSessionTo033Test.kt} | 4 +-- .../home/room/list/RoomListSectionBuilder.kt | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt rename matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/{MigrateSessionTo032Test.kt => MigrateSessionTo033Test.kt} (92%) 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 9be1717f32..b0f2ef34a7 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 @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -64,7 +65,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 32L + val schemaVersion = 33L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -101,5 +102,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 31) MigrateSessionTo031(realm).perform() if (oldVersion < 32) MigrateSessionTo032(realm).perform() + if (oldVersion < 33) MigrateSessionTo033(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt new file mode 100644 index 0000000000..b356bc6278 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo033(realm: DynamicRealm) : RealmMigrator(realm, 33) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt similarity index 92% rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt index 195b0ae1e3..a4ff995c44 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032Test.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt @@ -20,10 +20,10 @@ import org.junit.Test import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.test.fakes.FakeDynamicRealm -internal class MigrateSessionTo032Test { +internal class MigrateSessionTo033Test { private val fakeDynamicRealm = FakeDynamicRealm() - private val migrator = MigrateSessionTo032(fakeDynamicRealm.instance) + private val migrator = MigrateSessionTo033(fakeDynamicRealm.instance) @Test fun `when doMigrate, then directParentNames added`() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt index 42634c237a..f929366a5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt @@ -331,7 +331,7 @@ class RoomListSectionBuilder( }, { queryParams -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() From 7bb7a085a83d8b4dc4fce3961191605ac84f12da Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 15:38:03 +0200 Subject: [PATCH 019/133] Fixes copyright lint error --- .../java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt | 2 +- .../matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt | 2 +- .../org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt | 3 ++- .../java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt index 939b3be0c1..9292d6466e 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt index 1e0e1808b0..50bbe8c71d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt index 91f05b9604..da93d88041 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 The Matrix.org Foundation C.I.C. + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt index 519654b883..84ccd61857 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. From e6475a3f8369fa4b6b92efd6a5073c05dd9e17e4 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 15:47:19 +0200 Subject: [PATCH 020/133] Fixes copyright lint error --- .../sdk/internal/database/migration/MigrateSessionTo033Test.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt index a4ff995c44..5e994b2209 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 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. From bc5c53b70dee92e965b12f9d51c17c5c898812b0 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 18 Jul 2022 15:53:51 +0200 Subject: [PATCH 021/133] Fixes copyright lint error --- .../sdk/internal/database/migration/MigrateSessionTo033.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt index b356bc6278..a00855e262 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 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. From 6f9b5c010df77812a0acff6c28c9bfaa86f3e118 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 19 Jul 2022 13:49:21 +0200 Subject: [PATCH 022/133] Deletes migration test --- .../migration/MigrateSessionTo033Test.kt | 36 --------------- .../sdk/test/fakes/FakeDynamicRealm.kt | 30 ------------ .../sdk/test/fakes/FakeDynamicRealmObject.kt | 33 ------------- .../sdk/test/fakes/FakeRealmObjectSchema.kt | 46 ------------------- .../android/sdk/test/fakes/FakeRealmSchema.kt | 36 --------------- 5 files changed, 181 deletions(-) delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt deleted file mode 100644 index 5e994b2209..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033Test.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 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.migration - -import org.junit.Test -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields -import org.matrix.android.sdk.test.fakes.FakeDynamicRealm - -internal class MigrateSessionTo033Test { - - private val fakeDynamicRealm = FakeDynamicRealm() - private val migrator = MigrateSessionTo033(fakeDynamicRealm.instance) - - @Test - fun `when doMigrate, then directParentNames added`() { - migrator.doMigrate(fakeDynamicRealm.instance) - - fakeDynamicRealm.fakeRealmSchema.withObjectSchema("RoomSummaryEntity") - .verifyListFieldAdded(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) - .verifyStringTransformation(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt deleted file mode 100644 index 9292d6466e..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealm.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 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.test.fakes - -import io.mockk.every -import io.mockk.mockk -import io.realm.DynamicRealm - -class FakeDynamicRealm( - val fakeRealmSchema: FakeRealmSchema = FakeRealmSchema() -) { - - val instance: DynamicRealm = mockk { - every { schema } returns fakeRealmSchema.instance - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt deleted file mode 100644 index 50bbe8c71d..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeDynamicRealmObject.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 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.test.fakes - -import io.mockk.justRun -import io.mockk.mockk -import io.mockk.verify -import io.realm.DynamicRealmObject - -class FakeDynamicRealmObject { - - val instance: DynamicRealmObject = mockk { - justRun { setString(any(), any()) } - } - - fun verifySetString(fieldName: String, value: String) { - verify { instance.setString(fieldName, value) } - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt deleted file mode 100644 index da93d88041..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmObjectSchema.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * Copyright 2022 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.test.fakes - -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import io.mockk.verify -import io.realm.RealmObjectSchema -import io.realm.RealmObjectSchema.Function - -class FakeRealmObjectSchema( - private val fakeDynamicRealmObject: FakeDynamicRealmObject = FakeDynamicRealmObject() -) { - - val instance: RealmObjectSchema = mockk { - every { addRealmListField(any(), any>()) } returns this - every { transform(any()) } returns this - } - - fun verifyListFieldAdded(fieldName: String, type: Class<*>) = apply { - verify { instance.addRealmListField(fieldName, type) } - } - - fun verifyStringTransformation(fieldName: String, transformedInto: String) = apply { - val transformationSlot = slot() - verify { instance.transform(capture(transformationSlot)) } - transformationSlot.captured.apply(fakeDynamicRealmObject.instance) - fakeDynamicRealmObject.verifySetString(fieldName, transformedInto) - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt deleted file mode 100644 index 84ccd61857..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmSchema.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 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.test.fakes - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import io.realm.RealmSchema - -class FakeRealmSchema( - private val fakeRealmObjectSchema: FakeRealmObjectSchema = FakeRealmObjectSchema() -) { - - val instance: RealmSchema = mockk { - every { this@mockk.get(any()) } returns fakeRealmObjectSchema.instance - } - - fun withObjectSchema(className: String): FakeRealmObjectSchema { - verify { instance.get(className) } - return fakeRealmObjectSchema - } -} From f9580e6cd5c757f1ea92fbe66a3db2dd2a9b75b5 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 19 Jul 2022 14:12:56 +0200 Subject: [PATCH 023/133] Fixes error in RealmSessionStoreMigration --- .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 96fdfb96d2..9784412761 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 @@ -67,7 +67,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - override fun doMigrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateSessionTo001(realm).perform() if (oldVersion < 2) MigrateSessionTo002(realm).perform() if (oldVersion < 3) MigrateSessionTo003(realm).perform() From bc3ab9dd11f970bc11f0d4c807663dad01efe06d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Jul 2022 10:25:33 +0200 Subject: [PATCH 024/133] Remove duplicated and unused dependencies. --- dependencies.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 97b4ad2ea3..db5b3e19a1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -49,9 +49,7 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ - 'annotation' : "androidx.annotation:annotation:1.4.0", 'activity' : "androidx.activity:activity:1.5.0", - 'annotations' : "androidx.annotation:annotation:1.3.0", 'appCompat' : "androidx.appcompat:appcompat:1.4.2", 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.8.0", From 5f6f16d94b83f2ce92b2342c2b1aea36dfb0b9f8 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 20 Jul 2022 11:22:42 +0200 Subject: [PATCH 025/133] Adds migrate session to 034 --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo034.kt | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt 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 9784412761..64e69bb3e9 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 @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 33L, + schemaVersion = 34L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -101,5 +102,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 31) MigrateSessionTo031(realm).perform() if (oldVersion < 32) MigrateSessionTo032(realm).perform() if (oldVersion < 33) MigrateSessionTo033(realm).perform() + if (oldVersion < 34) MigrateSessionTo034(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt new file mode 100644 index 0000000000..f7c50477e5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo034(realm: DynamicRealm) : RealmMigrator(realm, 34) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } + } +} From 10b1443fda17b9b64884b077cbd4e44eeee12514 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 20 Jul 2022 12:12:32 +0200 Subject: [PATCH 026/133] add quick test for direct parent name --- .../sdk/session/space/SpaceHierarchyTest.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 80020665f8..18645fd6d9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -610,4 +610,82 @@ class SpaceHierarchyTest : InstrumentedTest { } } } + + @Test + fun testDirectParentNames() = runSessionTest(context()) { commonTestHelper -> + val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace( + commonTestHelper, + aliceSession, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) + ) + + val spaceBInfo = createPublicSpace( + commonTestHelper, + aliceSession, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) + ) + + // also add B1 in space A + + val B1roomId = spaceBInfo.roomIds.first() + val viaServers = listOf(aliceSession.sessionParams.homeServerHost ?: "") + + val spaceA = aliceSession.spaceService().getSpace(spaceAInfo.spaceId) + val spaceB = aliceSession.spaceService().getSpace(spaceBInfo.spaceId) + commonTestHelper.runBlockingTest { + spaceA!!.addChildren(B1roomId, viaServers, null, true) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(B1roomId) + roomSummary != null && + roomSummary.directParentNames.size == 2 && + roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name) && + roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name) + } + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first()) + roomSummary != null && + roomSummary.directParentNames.size == 1 && + roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name) + } + } + + val newAName = "FooBar" + commonTestHelper.runBlockingTest { + spaceA!!.asRoom().stateService().updateName(newAName) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(B1roomId) + roomSummary != null && + roomSummary.directParentNames.size == 2 && + roomSummary.directParentNames.contains(newAName) && + roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name) + } + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first()) + roomSummary != null && + roomSummary.directParentNames.size == 1 && + roomSummary.directParentNames.contains(newAName) + } + } + } } From 0ad2051dd369d7b1910ead6ff78856155b2b6ead Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 20 Jul 2022 14:48:38 +0200 Subject: [PATCH 027/133] Improves plural used in strings --- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 4 ++-- vector/src/main/res/values/strings.xml | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 a78c3d2fdc..85879e6807 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 @@ -217,8 +217,8 @@ class RoomSummaryItemFactory @Inject constructor( when (val size = directParentNames.size) { 0 -> null 1 -> directParentNames.first() - 2 -> stringProvider.getQuantityString(R.plurals.search_space_parents, 1, directParentNames[0], directParentNames[1]) - else -> stringProvider.getQuantityString(R.plurals.search_space_parents, size - 1, directParentNames[0], directParentNames.size - 1) + 2 -> stringProvider.getString(R.string.search_space_two_parents, directParentNames[0], directParentNames[1]) + else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1) } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 5243e1bd20..333a626179 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -764,8 +764,9 @@ Filter room members Filter banned users No results - - %1$s and %2$s + %1$s and %2$s + + %1$s and %2$d other %1$s and %2$d others From 372ff9ff978d71b4b4e36058fbf11b4579c43b84 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 20 Jul 2022 15:57:30 +0300 Subject: [PATCH 028/133] Move to initial zoom level when a user is focused. --- .../features/location/live/map/LocationLiveMapViewFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt index 1d6afa9cda..869ce0d388 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt @@ -273,7 +273,7 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment - mapboxMap?.get()?.zoomToLocation(locationData, preserveCurrentZoomLevel = true) + mapboxMap?.get()?.zoomToLocation(locationData, preserveCurrentZoomLevel = false) } } From 0f3e4046e12848701d22a7e4a8be28193afd3ec8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 20 Jul 2022 15:57:56 +0300 Subject: [PATCH 029/133] Animate camera while zooming to a user. --- .../im/vector/app/features/location/MapBoxMapExt.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt index cbfdf1dfda..8e917c665a 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt @@ -28,10 +28,12 @@ fun MapboxMap?.zoomToLocation(locationData: LocationData, preserveCurrentZoomLev } else { INITIAL_MAP_ZOOM_IN_PREVIEW } - this?.cameraPosition = CameraPosition.Builder() - .target(LatLng(locationData.latitude, locationData.longitude)) - .zoom(zoomLevel) - .build() + this?.easeCamera { + CameraPosition.Builder() + .target(LatLng(locationData.latitude, locationData.longitude)) + .zoom(zoomLevel) + .build() + } } fun MapboxMap?.zoomToBounds(latLngBounds: LatLngBounds) { From 5c359d4d45e9abc98872c43f4b1a5013c1afac5f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 20 Jul 2022 16:13:01 +0300 Subject: [PATCH 030/133] Changelog added. --- changelog.d/6609.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6609.misc diff --git a/changelog.d/6609.misc b/changelog.d/6609.misc new file mode 100644 index 0000000000..bf1a9efe14 --- /dev/null +++ b/changelog.d/6609.misc @@ -0,0 +1 @@ +Live Location Sharing - Reset zoom level while focusing a user From 10322545aafa703ef7ebe8db455c9c803536e2f3 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 20 Jul 2022 16:57:48 +0300 Subject: [PATCH 031/133] Move pin to center when clicked. --- .../location/live/map/LocationLiveMapViewFragment.kt | 9 ++++----- .../main/res/layout/fragment_location_live_map_view.xml | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt index 869ce0d388..e19580f13b 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt @@ -45,6 +45,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.openLocation import im.vector.app.databinding.FragmentLocationLiveMapViewBinding +import im.vector.app.features.location.LocationData import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.location.zoomToBounds import im.vector.app.features.location.zoomToLocation @@ -137,11 +138,9 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_gravity="center"/> Date: Wed, 20 Jul 2022 16:45:35 +0200 Subject: [PATCH 032/133] Adds MigrateSessionTo035 --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo035.kt | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt 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 64e69bb3e9..b733aa6fc0 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 @@ -51,6 +51,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -59,7 +60,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 34L, + schemaVersion = 35L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -103,5 +104,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 32) MigrateSessionTo032(realm).perform() if (oldVersion < 33) MigrateSessionTo033(realm).perform() if (oldVersion < 34) MigrateSessionTo034(realm).perform() + if (oldVersion < 35) MigrateSessionTo035(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt new file mode 100644 index 0000000000..f0d78786ed --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo035(realm: DynamicRealm) : RealmMigrator(realm, 35) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } + } +} From 3f637ea41a2b7abf49970fa6c4ae1efcb12dcfb5 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 20 Jul 2022 17:50:59 +0200 Subject: [PATCH 033/133] Fixes migration crash --- .../sdk/internal/database/migration/MigrateSessionTo035.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt index f0d78786ed..5b3c95b4a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm +import io.realm.RealmList import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator @@ -25,6 +26,6 @@ internal class MigrateSessionTo035(realm: DynamicRealm) : RealmMigrator(realm, 3 override fun doMigrate(realm: DynamicRealm) { realm.schema.get("RoomSummaryEntity") ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) - ?.transform { it.setString(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, "") } + ?.transform { it.setList(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, RealmList("")) } } } From 6b0f8318b7abf593ce1321d0dcb1438a78990112 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Jul 2022 23:39:37 +0200 Subject: [PATCH 034/133] Nightly: fix issue in towncrier command. Add missing `build` --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 483291de1f..36fd225674 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -37,7 +37,7 @@ jobs: mv towncrier.toml towncrier.toml.bak sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml rm towncrier.toml.bak - yes n | towncrier --version nightly + yes n | towncrier build --version nightly - name: Build and upload Gplay Nightly APK run: | ./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES --stacktrace From 7a3f2bb2407e180afdfefa7bfb4034440a8c398f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jul 2022 09:30:43 +0200 Subject: [PATCH 035/133] stable name for withheld --- changelog.d/5115.bugfix | 1 + .../matrix/android/sdk/api/session/events/model/EventType.kt | 2 +- .../android/sdk/internal/crypto/DefaultCryptoService.kt | 4 ++-- .../android/sdk/internal/crypto/IncomingKeyRequestManager.kt | 2 +- .../internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 2 +- .../crypto/store/db/model/OutgoingKeyRequestEntity.kt | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 changelog.d/5115.bugfix diff --git a/changelog.d/5115.bugfix b/changelog.d/5115.bugfix new file mode 100644 index 0000000000..6b3ca4a7b4 --- /dev/null +++ b/changelog.d/5115.bugfix @@ -0,0 +1 @@ +Stop using unstable names for withheld codes diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index fa3a9f6acd..656a20a62e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -87,7 +87,7 @@ object EventType { // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" - const val ROOM_KEY_WITHHELD = "org.matrix.room_key.withheld" + val ROOM_KEY_WITHHELD = listOf("m.room_key.withheld", "org.matrix.room_key.withheld") const val REQUEST_SECRET = "m.secret.request" const val SEND_SECRET = "m.secret.send" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 850a4379ca..42783eb8a2 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -820,7 +820,7 @@ internal class DefaultCryptoService @Inject constructor( EventType.SEND_SECRET -> { onSecretSendReceived(event) } - EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD -> { onKeyWithHeldReceived(event) } else -> { @@ -869,7 +869,7 @@ internal class DefaultCryptoService @Inject constructor( senderKey = withHeldContent.senderKey, fromDevice = withHeldContent.fromDevice, event = Event( - type = EventType.ROOM_KEY_WITHHELD, + type = EventType.ROOM_KEY_WITHHELD.first(), senderId = senderId, content = event.getClearContent() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 7f36224dae..5eec293ac6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -315,7 +315,7 @@ internal class IncomingKeyRequestManager @Inject constructor( ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, + EventType.ROOM_KEY_WITHHELD.first(), MXUsersDevicesMap().apply { setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index ceaee582c7..d524acbe81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -365,7 +365,7 @@ internal class MXMegolmEncryption( fromDevice = myDeviceId ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, + EventType.ROOM_KEY_WITHHELD.first(), MXUsersDevicesMap().apply { targets.forEach { setObject(it.userId, it.deviceId, withHeldContent) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt index 854d148b76..42cc5978cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt @@ -117,7 +117,7 @@ internal open class OutgoingKeyRequestEntity( private fun eventToResult(event: Event): RequestResult? { return when (event.getClearType()) { - EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD -> { event.content.toModel()?.code?.let { RequestResult.Failure(it) } From d51a1fdb0e2101abdcd18625b4a3ac41a1ffdb09 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jul 2022 09:35:50 +0200 Subject: [PATCH 036/133] add back withheld tests --- .../android/sdk/internal/crypto/gossiping/WithHeldTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index ae420a09b3..94fdddc1d9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -47,7 +47,6 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -@Ignore class WithHeldTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) From 687c2794fd03960ee0f670d9c092801ca11be02d Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 21 Jul 2022 10:13:15 +0200 Subject: [PATCH 037/133] use specific unstable/stable class --- .../crypto/gossiping/WithHeldTests.kt | 1 - .../sdk/api/session/events/model/EventType.kt | 5 +++- .../session/events/model/StableUnstableId.kt | 24 +++++++++++++++++++ .../internal/crypto/DefaultCryptoService.kt | 4 ++-- .../crypto/IncomingKeyRequestManager.kt | 2 +- .../algorithms/megolm/MXMegolmEncryption.kt | 2 +- .../db/model/OutgoingKeyRequestEntity.kt | 2 +- 7 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 94fdddc1d9..0aac4297e4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 656a20a62e..8fdbba21c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -87,7 +87,10 @@ object EventType { // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" - val ROOM_KEY_WITHHELD = listOf("m.room_key.withheld", "org.matrix.room_key.withheld") + val ROOM_KEY_WITHHELD = StableUnstableId( + stable = "m.room_key.withheld", + unstable = "org.matrix.room_key.withheld" + ) const val REQUEST_SECRET = "m.secret.request" const val SEND_SECRET = "m.secret.send" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt new file mode 100644 index 0000000000..c68a9e47f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 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.events.model + +data class StableUnstableId( + val stable: String, + val unstable: String, +) { + val values = listOf(stable, unstable) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 42783eb8a2..35c066dea8 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -820,7 +820,7 @@ internal class DefaultCryptoService @Inject constructor( EventType.SEND_SECRET -> { onSecretSendReceived(event) } - in EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD.values -> { onKeyWithHeldReceived(event) } else -> { @@ -869,7 +869,7 @@ internal class DefaultCryptoService @Inject constructor( senderKey = withHeldContent.senderKey, fromDevice = withHeldContent.fromDevice, event = Event( - type = EventType.ROOM_KEY_WITHHELD.first(), + type = EventType.ROOM_KEY_WITHHELD.stable, senderId = senderId, content = event.getClearContent() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 5eec293ac6..729b4481e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -315,7 +315,7 @@ internal class IncomingKeyRequestManager @Inject constructor( ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD.first(), + EventType.ROOM_KEY_WITHHELD.stable, MXUsersDevicesMap().apply { setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index d524acbe81..96d97a41d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -365,7 +365,7 @@ internal class MXMegolmEncryption( fromDevice = myDeviceId ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD.first(), + EventType.ROOM_KEY_WITHHELD.stable, MXUsersDevicesMap().apply { targets.forEach { setObject(it.userId, it.deviceId, withHeldContent) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt index 42cc5978cd..b10e7501d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt @@ -117,7 +117,7 @@ internal open class OutgoingKeyRequestEntity( private fun eventToResult(event: Event): RequestResult? { return when (event.getClearType()) { - in EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD.values -> { event.content.toModel()?.code?.let { RequestResult.Failure(it) } From d8fa1cb2eda17fa8f2b23f8360b1a5d46c6caf70 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:12:43 +0100 Subject: [PATCH 038/133] Fix a typo during registration terns -> terms --- vector/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ed0cf691e2..6da2aa54c2 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1943,7 +1943,7 @@ Server policies - Please read through %s\'s terns and policies + Please read through %s\'s terms and policies Enter your email From 96cfcc8229a8f5611047de60405c331d0b900305 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Thu, 21 Jul 2022 10:16:52 +0100 Subject: [PATCH 039/133] changelog --- changelog.d/6612.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6612.misc diff --git a/changelog.d/6612.misc b/changelog.d/6612.misc new file mode 100644 index 0000000000..ba80ff3e9d --- /dev/null +++ b/changelog.d/6612.misc @@ -0,0 +1 @@ +Fix a typo in the terms and conditions step during registration. From 0dd9e943c7096d30a26fed9108508ba4eaeafb99 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 Jul 2022 21:40:48 +0200 Subject: [PATCH 040/133] Delegate the creation of the Session to MainActivity instead of VectorApplication to unblock the main thread. This fix is not necessary anymore, since the session is restored either in `MainActivity` or in `VectorMessagingReceiver`. Ref: b7a54ead681c0396662d47b2df70605c16f9e0ad --- .../im/vector/app/features/MainViewModel.kt | 66 +++++++++++++++++++ .../java/im/vector/app/VectorApplication.kt | 18 ----- .../vector/app/core/di/ActiveSessionSetter.kt | 37 +++++++++++ .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../core/pushers/VectorMessagingReceiver.kt | 7 ++ .../im/vector/app/features/MainActivity.kt | 20 ++++++ 6 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/features/MainViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt diff --git a/vector/src/debug/java/im/vector/app/features/MainViewModel.kt b/vector/src/debug/java/im/vector/app/features/MainViewModel.kt new file mode 100644 index 0000000000..51fdabebc8 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/MainViewModel.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 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.features + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionSetter +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorDummyViewState +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +sealed interface MainViewAction : VectorViewModelAction { + object StartApp : MainViewAction +} + +sealed interface MainViewEvent : VectorViewEvents { + object AppStarted : MainViewEvent +} + +class MainViewModel @AssistedInject constructor( + @Assisted val initialState: VectorDummyViewState, + private val activeSessionSetter: ActiveSessionSetter, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: VectorDummyViewState): MainViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: MainViewAction) { + when (action) { + MainViewAction.StartApp -> handleStartApp() + } + } + + private fun handleStartApp() { + viewModelScope.launch(Dispatchers.IO) { + // This can take time because of DB migration(s), so do it in a background task. + activeSessionSetter.tryToSetActiveSession(startSync = true) + _viewEvents.post(MainViewEvent.AppStarted) + } + } +} diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 3cb0423ca8..24d9770604 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -41,8 +41,6 @@ import com.vanniktech.emoji.EmojiManager import com.vanniktech.emoji.google.GoogleEmojiProvider import dagger.hilt.android.HiltAndroidApp import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.startSyncing import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration @@ -165,14 +163,6 @@ class VectorApplication : doNotShowDisclaimerDialog(this) } - if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { - val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! - activeSessionHolder.setActiveSession(lastAuthenticatedSession) - lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = false) - } - - ProcessLifecycleOwner.get().lifecycle.addObserver(startSyncOnFirstStart) - ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { Timber.i("App entered foreground") @@ -205,14 +195,6 @@ class VectorApplication : Mapbox.getInstance(this) } - private val startSyncOnFirstStart = object : DefaultLifecycleObserver { - override fun onStart(owner: LifecycleOwner) { - Timber.i("App process started") - authenticationService.getLastAuthenticatedSession()?.startSyncing(appContext) - ProcessLifecycleOwner.get().lifecycle.removeObserver(this) - } - } - private fun enableStrictModeIfNeeded() { if (BuildConfig.ENABLE_STRICT_MODE_LOGS) { StrictMode.setThreadPolicy( diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt new file mode 100644 index 0000000000..59c2037cbe --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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.di + +import android.content.Context +import im.vector.app.core.extensions.configureAndStart +import org.matrix.android.sdk.api.auth.AuthenticationService +import javax.inject.Inject + +class ActiveSessionSetter @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, + private val authenticationService: AuthenticationService, + private val applicationContext: Context, +) { + fun tryToSetActiveSession(startSync: Boolean) { + if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { + val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! + activeSessionHolder.setActiveSession(lastAuthenticatedSession) + lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = startSync) + activeSessionHolder.setActiveSession(lastAuthenticatedSession) + } + } +} diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index a3e08036ff..3cd8dc66e9 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -20,6 +20,7 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.multibindings.IntoMap +import im.vector.app.features.MainViewModel import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel import im.vector.app.features.auth.ReAuthViewModel @@ -483,6 +484,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(AnalyticsAccountDataViewModel::class) fun analyticsAccountDataViewModelFactory(factory: AnalyticsAccountDataViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds + @IntoMap + @MavericksViewModelKey(MainViewModel::class) + fun mainViewModelFactory(factory: MainViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds @IntoMap @MavericksViewModelKey(HomeServerCapabilitiesViewModel::class) diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt index 53a5470ff7..be84dfeaba 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt @@ -27,6 +27,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import dagger.hilt.android.AndroidEntryPoint import im.vector.app.BuildConfig import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.di.ActiveSessionSetter import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.model.PushData import im.vector.app.core.services.GuardServiceStarter @@ -59,6 +60,7 @@ class VectorMessagingReceiver : MessagingReceiver() { @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notifiableEventResolver: NotifiableEventResolver @Inject lateinit var pushersManager: PushersManager + @Inject lateinit var activeSessionSetter: ActiveSessionSetter @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorDataStore: VectorDataStore @@ -177,6 +179,11 @@ class VectorMessagingReceiver : MessagingReceiver() { } val session = activeSessionHolder.getSafeActiveSession() + ?: run { + // Active session may not exist yet, if MainActivity has not been launched + activeSessionSetter.tryToSetActiveSession(startSync = false) + activeSessionHolder.getSafeActiveSession() + } if (session == null) { Timber.tag(loggerTag.value).w("## Can't sync from push, no current session") diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index c2f6f2d778..6bc0a2ca86 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.os.Parcelable import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.viewModel import com.bumptech.glide.Glide import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -47,6 +48,8 @@ import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -84,6 +87,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } + private val mainViewModel: MainViewModel by viewModel() + override fun getBinding() = ActivityMainBinding.inflate(layoutInflater) override fun getOtherThemes() = ActivityOtherThemes.Launcher @@ -103,6 +108,21 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + mainViewModel.viewEvents.stream() + .onEach(::handleViewEvents) + .launchIn(lifecycleScope) + + mainViewModel.handle(MainViewAction.StartApp) + } + + private fun handleViewEvents(event: MainViewEvent) { + when (event) { + MainViewEvent.AppStarted -> handleAppStarted() + } + } + + private fun handleAppStarted() { args = parseArgs() if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) { clearNotifications() From c8a2bfc7385db9fb79c6035f6acf5458701fcfe4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 Jul 2022 21:01:28 +0200 Subject: [PATCH 041/133] Always start MainActivity to ensure that current session is set. --- .../im/vector/app/features/MainActivity.kt | 34 ++++++++++++++----- .../app/features/call/VectorCallActivity.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 4 ++- .../home/room/detail/RoomDetailActivity.kt | 10 ++++-- .../home/room/detail/TimelineFragment.kt | 6 ++-- .../features/navigation/DefaultNavigator.kt | 6 ++-- .../notifications/NotificationUtils.kt | 11 +++--- 7 files changed, 51 insertions(+), 22 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 6bc0a2ca86..c0af9f9e70 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -17,6 +17,7 @@ package im.vector.app.features import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable @@ -76,6 +77,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity companion object { private const val EXTRA_ARGS = "EXTRA_ARGS" + private const val EXTRA_NEXT_INTENT = "EXTRA_NEXT_INTENT" // Special action to clear cache and/or clear credentials fun restartApp(activity: Activity, args: MainActivityArgs) { @@ -85,6 +87,12 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity intent.putExtra(EXTRA_ARGS, args) activity.startActivity(intent) } + + fun getIntentWithNextIntent(context: Context, nextIntent: Intent): Intent { + val intent = Intent(context, MainActivity::class.java) + intent.putExtra(EXTRA_NEXT_INTENT, nextIntent) + return intent + } } private val mainViewModel: MainViewModel by viewModel() @@ -123,15 +131,21 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } private fun handleAppStarted() { - args = parseArgs() - if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) { - clearNotifications() - } - // Handle some wanted cleanup - if (args.clearCache || args.clearCredentials) { - doCleanUp() + if (intent.hasExtra(EXTRA_NEXT_INTENT)) { + // Start the next Activity + val nextIntent = intent.getParcelableExtra(EXTRA_NEXT_INTENT) + startIntentAndFinish(nextIntent) } else { - startNextActivityAndFinish() + args = parseArgs() + if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) { + clearNotifications() + } + // Handle some wanted cleanup + if (args.clearCache || args.clearCredentials) { + doCleanUp() + } else { + startNextActivityAndFinish() + } } } @@ -273,6 +287,10 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity null } } + startIntentAndFinish(intent) + } + + private fun startIntentAndFinish(intent: Intent?) { intent?.let { startActivity(it) } finish() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 9d7ada9d63..f8a4c5eeca 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -604,7 +604,7 @@ class VectorCallActivity : private fun returnToChat() { val roomId = withState(callViewModel) { it.roomId } val args = TimelineArgs(roomId) - val intent = RoomDetailActivity.newIntent(this, args).apply { + val intent = RoomDetailActivity.newIntent(this, args, false).apply { flags = FLAG_ACTIVITY_CLEAR_TOP } startActivity(intent) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 926c1eb113..0eb136fc10 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -623,10 +623,12 @@ class HomeActivity : inviteNotificationRoomId = inviteNotificationRoomId ) - return Intent(context, HomeActivity::class.java) + val intent = Intent(context, HomeActivity::class.java) .apply { putExtra(Mavericks.KEY_ARG, args) } + + return MainActivity.getIntentWithNextIntent(context, intent) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index f1e06dd5ef..a58eed42e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -35,6 +35,7 @@ import im.vector.app.core.extensions.keepScreenOn import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityRoomDetailBinding +import im.vector.app.features.MainActivity import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment @@ -191,10 +192,15 @@ class RoomDetailActivity : const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID" const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT" - fun newIntent(context: Context, timelineArgs: TimelineArgs): Intent { - return Intent(context, RoomDetailActivity::class.java).apply { + fun newIntent(context: Context, timelineArgs: TimelineArgs, firstStartMainActivity: Boolean): Intent { + val intent = Intent(context, RoomDetailActivity::class.java).apply { putExtra(EXTRA_ROOM_DETAIL_ARGS, timelineArgs) } + return if (firstStartMainActivity) { + MainActivity.getIntentWithNextIntent(context, intent) + } else { + intent + } } // Shortcuts can't have intents with parcelables diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 31c1004ef9..562f2d4aea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1206,9 +1206,9 @@ class TimelineFragment @Inject constructor( getRootThreadEventId()?.let { val newRoom = timelineArgs.copy(threadTimelineArgs = null, eventId = it) context?.let { con -> - val int = RoomDetailActivity.newIntent(con, newRoom) - int.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK - con.startActivity(int) + val intent = RoomDetailActivity.newIntent(con, newRoom, false) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + con.startActivity(intent) } } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 291eee307f..7680b40506 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -173,7 +173,7 @@ class DefaultNavigator @Inject constructor( } val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted) - val intent = RoomDetailActivity.newIntent(context, args) + val intent = RoomDetailActivity.newIntent(context, args, false) startActivity(context, intent, buildTask) } @@ -203,7 +203,7 @@ class DefaultNavigator @Inject constructor( eventId = null, openShareSpaceForId = spaceId.takeIf { postSwitchSpaceAction.showShareSheet } ) - val intent = RoomDetailActivity.newIntent(context, args) + val intent = RoomDetailActivity.newIntent(context, args, false) startActivity(context, intent, false) } } @@ -290,7 +290,7 @@ class DefaultNavigator @Inject constructor( override fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) { val args = TimelineArgs(roomId, null, sharedData) - val intent = RoomDetailActivity.newIntent(activity, args) + val intent = RoomDetailActivity.newIntent(activity, args, false) activity.startActivity(intent) activity.finish() } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 71c8167788..0066fab5ce 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -53,6 +53,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.services.CallAndroidService import im.vector.app.core.time.Clock import im.vector.app.core.utils.startNotificationChannelSettingsIntent +import im.vector.app.features.MainActivity import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.service.CallHeadsUpActionReceiver import im.vector.app.features.call.webrtc.WebRtcCall @@ -241,7 +242,8 @@ class NotificationUtils @Inject constructor( // build the pending intent go to the home screen if this is clicked. val i = HomeActivity.newIntent(context) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - val pi = PendingIntent.getActivity(context, 0, i, PendingIntentCompat.FLAG_IMMUTABLE) + val mainIntent = MainActivity.getIntentWithNextIntent(context, i) + val pi = PendingIntent.getActivity(context, 0, mainIntent, PendingIntentCompat.FLAG_IMMUTABLE) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) @@ -531,7 +533,7 @@ class NotificationUtils @Inject constructor( val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) - .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId))) + .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId), true)) .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) builder.setContentIntent(contentPendingIntent) @@ -828,7 +830,7 @@ class NotificationUtils @Inject constructor( } private fun buildOpenRoomIntent(roomId: String): PendingIntent? { - val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true)) + val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true), true) roomIntentTap.action = TAP_TO_VIEW_ACTION // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that roomIntentTap.data = createIgnoredUri("openRoom?$roomId") @@ -847,10 +849,11 @@ class NotificationUtils @Inject constructor( val intent = HomeActivity.newIntent(context, clearNotification = true) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP intent.data = createIgnoredUri("tapSummary") + val mainIntent = MainActivity.getIntentWithNextIntent(context, intent) return PendingIntent.getActivity( context, Random.nextInt(1000), - intent, + mainIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) } From b7826c02a3c32f9516554c74de02559c18a23e19 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Jul 2022 17:17:00 +0200 Subject: [PATCH 042/133] Start SDK before handling permalink or sharing to the app. It also fixes a crash when trying to share to the app if there is no active session: `IncomingShareViewModel` injects the `session` in the constructor. --- .../vector/app/core/di/ActiveSessionSetter.kt | 7 ++- .../app/core/di/MavericksViewModelModule.kt | 6 +-- .../im/vector/app/features/MainActivity.kt | 23 +++++++-- .../app/features/link/LinkHandlerActivity.kt | 26 ++++++++++ .../app/features/share/IncomingShareAction.kt | 2 +- .../features/share/IncomingShareActivity.kt | 51 +++++++++++++++++-- .../features/share/IncomingShareFragment.kt | 19 +------ .../features/share/IncomingShareViewModel.kt | 4 +- .../app/features/start/StartAppAction.kt | 23 +++++++++ .../app/features/start/StartAppViewEvent.kt | 23 +++++++++ .../app/features/start/StartAppViewModel.kt} | 35 +++++-------- .../app/features/start/StartAppViewState.kt | 23 +++++++++ 12 files changed, 188 insertions(+), 54 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt rename vector/src/{debug/java/im/vector/app/features/MainViewModel.kt => main/java/im/vector/app/features/start/StartAppViewModel.kt} (58%) create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt index 59c2037cbe..09479a230f 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt @@ -26,12 +26,15 @@ class ActiveSessionSetter @Inject constructor( private val authenticationService: AuthenticationService, private val applicationContext: Context, ) { + fun shouldSetActionSession(): Boolean { + return authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession() + } + fun tryToSetActiveSession(startSync: Boolean) { - if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { + if (shouldSetActionSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = startSync) - activeSessionHolder.setActiveSession(lastAuthenticatedSession) } } } diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 3cd8dc66e9..a5fa04f1b0 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -20,7 +20,6 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.multibindings.IntoMap -import im.vector.app.features.MainViewModel import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel import im.vector.app.features.auth.ReAuthViewModel @@ -112,6 +111,7 @@ import im.vector.app.features.spaces.manage.SpaceManageSharedViewModel import im.vector.app.features.spaces.people.SpacePeopleViewModel import im.vector.app.features.spaces.preview.SpacePreviewViewModel import im.vector.app.features.spaces.share.ShareSpaceViewModel +import im.vector.app.features.start.StartAppViewModel import im.vector.app.features.terms.ReviewTermsViewModel import im.vector.app.features.usercode.UserCodeSharedViewModel import im.vector.app.features.userdirectory.UserListViewModel @@ -486,8 +486,8 @@ interface MavericksViewModelModule { @Binds @IntoMap - @MavericksViewModelKey(MainViewModel::class) - fun mainViewModelFactory(factory: MainViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @MavericksViewModelKey(StartAppViewModel::class) + fun startAppViewModelFactory(factory: StartAppViewModel.Factory): MavericksAssistedViewModelFactory<*, *> @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index c0af9f9e70..f160e77aa0 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -46,6 +46,9 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.session.VectorSessionStore import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.signout.hard.SignedOutActivity +import im.vector.app.features.start.StartAppAction +import im.vector.app.features.start.StartAppViewEvent +import im.vector.app.features.start.StartAppViewModel import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers @@ -78,6 +81,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity companion object { private const val EXTRA_ARGS = "EXTRA_ARGS" private const val EXTRA_NEXT_INTENT = "EXTRA_NEXT_INTENT" + private const val EXTRA_INIT_SESSION = "EXTRA_INIT_SESSION" // Special action to clear cache and/or clear credentials fun restartApp(activity: Activity, args: MainActivityArgs) { @@ -88,6 +92,12 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity activity.startActivity(intent) } + fun getIntentToInitSession(activity: Activity): Intent { + val intent = Intent(activity, MainActivity::class.java) + intent.putExtra(EXTRA_INIT_SESSION, true) + return intent + } + fun getIntentWithNextIntent(context: Context, nextIntent: Intent): Intent { val intent = Intent(context, MainActivity::class.java) intent.putExtra(EXTRA_NEXT_INTENT, nextIntent) @@ -95,7 +105,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } - private val mainViewModel: MainViewModel by viewModel() + private val startAppViewModel: StartAppViewModel by viewModel() override fun getBinding() = ActivityMainBinding.inflate(layoutInflater) @@ -117,16 +127,16 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mainViewModel.viewEvents.stream() + startAppViewModel.viewEvents.stream() .onEach(::handleViewEvents) .launchIn(lifecycleScope) - mainViewModel.handle(MainViewAction.StartApp) + startAppViewModel.handle(StartAppAction.StartApp) } - private fun handleViewEvents(event: MainViewEvent) { + private fun handleViewEvents(event: StartAppViewEvent) { when (event) { - MainViewEvent.AppStarted -> handleAppStarted() + StartAppViewEvent.AppStarted -> handleAppStarted() } } @@ -135,6 +145,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity // Start the next Activity val nextIntent = intent.getParcelableExtra(EXTRA_NEXT_INTENT) startIntentAndFinish(nextIntent) + } else if (intent.hasExtra(EXTRA_INIT_SESSION)) { + setResult(RESULT_OK) + finish() } else { args = parseArgs() if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) { diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 6de73cb20f..0bdec53f60 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -18,18 +18,23 @@ package im.vector.app.features.link import android.content.Intent import android.net.Uri +import android.os.Bundle import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.databinding.ActivityProgressBinding +import im.vector.app.features.MainActivity import im.vector.app.features.home.HomeActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.permalink.PermalinkHandler +import im.vector.app.features.start.StartAppViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.permalinks.PermalinkService import timber.log.Timber @@ -45,12 +50,33 @@ class LinkHandlerActivity : VectorBaseActivity() { @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var permalinkHandler: PermalinkHandler + private val startAppViewModel: StartAppViewModel by viewModel() + override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater) override fun initUiAndData() { handleIntent() } + private val launcher = registerStartForActivityResult { + if (it.resultCode == RESULT_OK) { + handleIntent() + } else { + // User has pressed back on the MainActivity, so finish also this one. + finish() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (startAppViewModel.shouldStartApp()) { + launcher.launch(MainActivity.getIntentToInitSession(this)) + } else { + handleIntent() + } + } + override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) handleIntent() diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt index 4e9f024b15..70be2b2b6d 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary sealed class IncomingShareAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary, val enableMultiSelect: Boolean) : IncomingShareAction() object ShareToSelectedRooms : IncomingShareAction() - data class ShareToRoom(val roomSummary: RoomSummary) : IncomingShareAction() + data class ShareToRoom(val roomId: String) : IncomingShareAction() data class ShareMedia(val keepOriginalSize: Boolean) : IncomingShareAction() data class FilterWith(val filter: String) : IncomingShareAction() data class UpdateSharedData(val sharedData: SharedData) : IncomingShareAction() diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt index 439d9b64fa..3d603e3f6a 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt @@ -16,21 +16,66 @@ package im.vector.app.features.share +import android.content.Intent +import android.os.Bundle +import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.MainActivity +import im.vector.app.features.start.StartAppViewModel +import javax.inject.Inject @AndroidEntryPoint class IncomingShareActivity : VectorBaseActivity() { + private val startAppViewModel: StartAppViewModel by viewModel() + + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + + private val launcher = registerStartForActivityResult { + if (it.resultCode == RESULT_OK) { + handleAppStarted() + } else { + // User has pressed back on the MainActivity, so finish also this one. + finish() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (startAppViewModel.shouldStartApp()) { + launcher.launch(MainActivity.getIntentToInitSession(this)) + } else { + handleAppStarted() + } + } + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) override fun getCoordinatorLayout() = views.coordinatorLayout - override fun initUiAndData() { - if (isFirstCreation()) { - addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java) + private fun handleAppStarted() { + // If we are not logged in, stop the sharing process and open login screen. + // In the future, we might want to relaunch the sharing process after login. + if (!activeSessionHolder.hasActiveSession()) { + startLoginActivity() + } else { + if (isFirstCreation()) { + addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java) + } } } + + private fun startLoginActivity() { + navigator.openLogin( + context = this, + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + ) + finish() + } } diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index 3f8923dd68..3e2ddc469c 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -30,7 +30,6 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.registerStartForActivityResult @@ -40,7 +39,6 @@ import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.attachments.ShareIntentHandler import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs -import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -50,7 +48,6 @@ import javax.inject.Inject */ class IncomingShareFragment @Inject constructor( private val incomingShareController: IncomingShareController, - private val sessionHolder: ActiveSessionHolder, private val shareIntentHandler: ShareIntentHandler, ) : VectorBaseFragment(), @@ -63,12 +60,6 @@ class IncomingShareFragment @Inject constructor( } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - // If we are not logged in, stop the sharing process and open login screen. - // In the future, we might want to relaunch the sharing process after login. - if (!sessionHolder.hasActiveSession()) { - startLoginActivity() - return - } super.onViewCreated(view, savedInstanceState) setupRecyclerView() setupToolbar(views.incomingShareToolbar) @@ -88,7 +79,7 @@ class IncomingShareFragment @Inject constructor( // Direct share if (intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) { val roomId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)!! - sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)?.let { viewModel.handle(IncomingShareAction.ShareToRoom(it)) } + viewModel.handle(IncomingShareAction.ShareToRoom(roomId)) } isShareManaged } @@ -192,14 +183,6 @@ class IncomingShareFragment @Inject constructor( .show() } - private fun startLoginActivity() { - navigator.openLogin( - context = requireActivity(), - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK - ) - requireActivity().finish() - } - override fun invalidate() = withState(viewModel) { views.sendShareButton.isVisible = it.isInMultiSelectionMode incomingShareController.setData(it) diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 1191fd04e8..85629ea150 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -134,7 +135,8 @@ class IncomingShareViewModel @AssistedInject constructor( private fun handleShareToRoom(action: IncomingShareAction.ShareToRoom) = withState { state -> val sharedData = state.sharedData ?: return@withState - _viewEvents.post(IncomingShareViewEvents.ShareToRoom(action.roomSummary, sharedData, showAlert = false)) + val roomSummary = session.getRoomSummary(action.roomId) ?: return@withState + _viewEvents.post(IncomingShareViewEvents.ShareToRoom(roomSummary, sharedData, showAlert = false)) } private fun handleShareMediaToSelectedRooms(action: IncomingShareAction.ShareMedia) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt new file mode 100644 index 0000000000..fffb124f12 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 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.features.start + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface StartAppAction : VectorViewModelAction { + object StartApp : StartAppAction +} diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt new file mode 100644 index 0000000000..9e185cbe04 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 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.features.start + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface StartAppViewEvent : VectorViewEvents { + object AppStarted : StartAppViewEvent +} diff --git a/vector/src/debug/java/im/vector/app/features/MainViewModel.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt similarity index 58% rename from vector/src/debug/java/im/vector/app/features/MainViewModel.kt rename to vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt index 51fdabebc8..eeec52283d 100644 --- a/vector/src/debug/java/im/vector/app/features/MainViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features +package im.vector.app.features.start import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -23,36 +23,29 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.ActiveSessionSetter import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.VectorDummyViewState -import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -sealed interface MainViewAction : VectorViewModelAction { - object StartApp : MainViewAction -} - -sealed interface MainViewEvent : VectorViewEvents { - object AppStarted : MainViewEvent -} - -class MainViewModel @AssistedInject constructor( - @Assisted val initialState: VectorDummyViewState, +class StartAppViewModel @AssistedInject constructor( + @Assisted val initialState: StartAppViewState, private val activeSessionSetter: ActiveSessionSetter, -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: VectorDummyViewState): MainViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: StartAppViewState): StartAppViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - override fun handle(action: MainViewAction) { + fun shouldStartApp(): Boolean { + return activeSessionSetter.shouldSetActionSession() + } + + override fun handle(action: StartAppAction) { when (action) { - MainViewAction.StartApp -> handleStartApp() + StartAppAction.StartApp -> handleStartApp() } } @@ -60,7 +53,7 @@ class MainViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { // This can take time because of DB migration(s), so do it in a background task. activeSessionSetter.tryToSetActiveSession(startSync = true) - _viewEvents.post(MainViewEvent.AppStarted) + _viewEvents.post(StartAppViewEvent.AppStarted) } } } diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt new file mode 100644 index 0000000000..50418e90dc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 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.features.start + +import com.airbnb.mvrx.MavericksState + +data class StartAppViewState( + val duration: Long = 0 +) : MavericksState From 0bedfc80878d8ef3ec5c3a6d6dda46d8951df57c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Jul 2022 17:32:49 +0200 Subject: [PATCH 043/133] Add a message when migrating the data (after 1 seconds) --- .../im/vector/app/features/MainActivity.kt | 12 ++++++++++++ .../app/features/start/StartAppViewModel.kt | 14 ++++++++++++++ vector/src/main/res/layout/activity_main.xml | 19 +++++++++++++++++-- vector/src/main/res/values/strings.xml | 1 + 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index f160e77aa0..805a52958a 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel @@ -49,6 +50,7 @@ import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.start.StartAppAction import im.vector.app.features.start.StartAppViewEvent import im.vector.app.features.start.StartAppViewModel +import im.vector.app.features.start.StartAppViewState import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers @@ -127,6 +129,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + startAppViewModel.onEach { + renderState(it) + } startAppViewModel.viewEvents.stream() .onEach(::handleViewEvents) .launchIn(lifecycleScope) @@ -134,6 +139,13 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity startAppViewModel.handle(StartAppAction.StartApp) } + private fun renderState(state: StartAppViewState) { + if (state.duration > 0) { + views.status.setText(R.string.updating_your_data) + } + views.status.isVisible = state.duration > 0 + } + private fun handleViewEvents(event: StartAppViewEvent) { when (event) { StartAppViewEvent.AppStarted -> handleAppStarted() diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt index eeec52283d..b66a072195 100644 --- a/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt @@ -24,8 +24,12 @@ import im.vector.app.core.di.ActiveSessionSetter import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.lib.core.utils.flow.tickerFlow import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds class StartAppViewModel @AssistedInject constructor( @Assisted val initialState: StartAppViewState, @@ -50,10 +54,20 @@ class StartAppViewModel @AssistedInject constructor( } private fun handleStartApp() { + startTimer() viewModelScope.launch(Dispatchers.IO) { // This can take time because of DB migration(s), so do it in a background task. activeSessionSetter.tryToSetActiveSession(startSync = true) _viewEvents.post(StartAppViewEvent.AppStarted) } } + + private fun startTimer() { + setState { copy(duration = 0) } + tickerFlow(viewModelScope, 1.seconds.inWholeMilliseconds) + .onEach { + setState { copy(duration = duration + 1) } + } + .launchIn(viewModelScope) + } } diff --git a/vector/src/main/res/layout/activity_main.xml b/vector/src/main/res/layout/activity_main.xml index c7bca50acb..ba5925f000 100644 --- a/vector/src/main/res/layout/activity_main.xml +++ b/vector/src/main/res/layout/activity_main.xml @@ -1,5 +1,4 @@ - - + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ed0cf691e2..cf844d29b0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1625,6 +1625,7 @@ No network. Please check your Internet connection. "Change network" "Please wait…" + Updating your data… "All Communities" From b294c9a1fd82a1ef23e618079109c979e6d07770 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 18 Jul 2022 15:00:51 +0200 Subject: [PATCH 044/133] Do not always start MainActivity first when launching HomeActivity --- .../im/vector/app/features/MainActivity.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 7 ++++++- .../app/features/login/LoginActivity.kt | 2 +- .../notifications/NotificationUtils.kt | 20 +++++++++++-------- .../app/features/onboarding/Login2Variant.kt | 3 ++- .../onboarding/ftueauth/FtueAuthVariant.kt | 6 +++++- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 805a52958a..968d3670a9 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -300,7 +300,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity // We have a session. // Check it can be opened if (sessionHolder.getActiveSession().isOpenable) { - HomeActivity.newIntent(this, existingSession = true) + HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true) } else { // The token is still invalid navigator.softLogout(this) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 0eb136fc10..ff114e4db9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -611,6 +611,7 @@ class HomeActivity : companion object { fun newIntent( context: Context, + firstStartMainActivity: Boolean, clearNotification: Boolean = false, authenticationDescription: AuthenticationDescription? = null, existingSession: Boolean = false, @@ -628,7 +629,11 @@ class HomeActivity : putExtra(Mavericks.KEY_ARG, args) } - return MainActivity.getIntentWithNextIntent(context, intent) + return if (firstStartMainActivity) { + MainActivity.getIntentWithNextIntent(context, intent) + } else { + intent + } } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index 4cbebd67a3..763d1eed38 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -221,7 +221,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA analyticsScreenName = MobileScreen.ScreenName.Register } val authDescription = inferAuthDescription(loginViewState) - val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription) + val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, authenticationDescription = authDescription) startActivity(intent) finish() return diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 0066fab5ce..514952b66c 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -240,7 +240,7 @@ class NotificationUtils @Inject constructor( @SuppressLint("NewApi") fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification { // build the pending intent go to the home screen if this is clicked. - val i = HomeActivity.newIntent(context) + val i = HomeActivity.newIntent(context, firstStartMainActivity = false) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val mainIntent = MainActivity.getIntentWithNextIntent(context, i) val pi = PendingIntent.getActivity(context, 0, mainIntent, PendingIntentCompat.FLAG_IMMUTABLE) @@ -346,7 +346,7 @@ class NotificationUtils @Inject constructor( ) val answerCallPendingIntent = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(HomeActivity.newIntent(context)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false)) .addNextIntent( VectorCallActivity.newIntent( context = context, @@ -470,7 +470,7 @@ class NotificationUtils @Inject constructor( ) val contentPendingIntent = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(HomeActivity.newIntent(context)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false)) .addNextIntent(VectorCallActivity.newIntent(context, call, null)) .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) @@ -532,7 +532,7 @@ class NotificationUtils @Inject constructor( .setCategory(NotificationCompat.CATEGORY_CALL) val contentPendingIntent = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(HomeActivity.newIntent(context)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false)) .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId), true)) .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) @@ -767,7 +767,11 @@ class NotificationUtils @Inject constructor( joinIntentPendingIntent ) - val contentIntent = HomeActivity.newIntent(context, inviteNotificationRoomId = inviteNotifiableEvent.roomId) + val contentIntent = HomeActivity.newIntent( + context, + firstStartMainActivity = true, + inviteNotificationRoomId = inviteNotifiableEvent.roomId + ) contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId) @@ -808,7 +812,7 @@ class NotificationUtils @Inject constructor( .setColor(accentColor) .setAutoCancel(true) .apply { - val contentIntent = HomeActivity.newIntent(context) + val contentIntent = HomeActivity.newIntent(context, firstStartMainActivity = true) contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = createIgnoredUri(simpleNotifiableEvent.eventId) @@ -837,7 +841,7 @@ class NotificationUtils @Inject constructor( // Recreate the back stack return TaskStackBuilder.create(context) - .addNextIntentWithParentStack(HomeActivity.newIntent(context)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false)) .addNextIntent(roomIntentTap) .getPendingIntent( clock.epochMillis().toInt(), @@ -846,7 +850,7 @@ class NotificationUtils @Inject constructor( } private fun buildOpenHomePendingIntentForSummary(): PendingIntent { - val intent = HomeActivity.newIntent(context, clearNotification = true) + val intent = HomeActivity.newIntent(context, firstStartMainActivity = false, clearNotification = true) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP intent.data = createIgnoredUri("tapSummary") val mainIntent = MainActivity.getIntentWithNextIntent(context, intent) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt index 0d7c83e360..7def6d62f0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt @@ -302,7 +302,8 @@ class Login2Variant( private fun terminate() { val intent = HomeActivity.newIntent( - activity + activity, + firstStartMainActivity = false, ) activity.startActivity(intent) activity.finish() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index bb8c523b5f..867ab45834 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -482,7 +482,11 @@ class FtueAuthVariant( private fun navigateToHome() { withState(onboardingViewModel) { - val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description) + val intent = HomeActivity.newIntent( + activity, + firstStartMainActivity = false, + authenticationDescription = it.selectedAuthenticationState.description + ) activity.startActivity(intent) activity.finish() } From 9dda647c52edb618a17fdc4a9ca000a4505ea5e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Jul 2022 16:46:04 +0200 Subject: [PATCH 045/133] Add Foreground service when data are updated. --- vector/src/main/AndroidManifest.xml | 5 ++ .../im/vector/app/features/MainActivity.kt | 7 +++ .../notifications/NotificationUtils.kt | 13 ++++ .../features/start/StartAppAndroidService.kt | 63 +++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 1c104f3bbf..b7bdac6879 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -380,6 +380,11 @@ android:exported="false" android:foregroundServiceType="location" /> + + (), UnlockedActivity views.status.setText(R.string.updating_your_data) } views.status.isVisible = state.duration > 0 + if (state.duration == 1L && startAppViewModel.shouldStartApp()) { + // Start foreground service, because the operation may take a while + val intent = Intent(this, StartAppAndroidService::class.java) + ContextCompat.startForegroundService(this, intent) + } } private fun handleViewEvents(event: StartAppViewEvent) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 514952b66c..2948565d58 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -568,6 +568,19 @@ class NotificationUtils @Inject constructor( .build() } + /** + * Creates a notification that indicates the application is initializing. + */ + fun buildStartAppNotification(): Notification { + return NotificationCompat.Builder(context, LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.updating_your_data)) + .setSmallIcon(R.drawable.sync) + .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary)) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_LOW) + .build() + } + fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setGroup(stringProvider.getString(R.string.app_name)) diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt b/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt new file mode 100644 index 0000000000..e8e0eac863 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 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.features.start + +import android.content.Intent +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.di.NamedGlobalScope +import im.vector.app.core.services.VectorAndroidService +import im.vector.app.features.notifications.NotificationUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.random.Random +import kotlin.time.Duration.Companion.seconds + +/** + * A simple foreground service that let the app (and the SDK) time to initialize. + * Will self stop itself once the active session is set. + */ +@AndroidEntryPoint +class StartAppAndroidService : VectorAndroidService() { + + @NamedGlobalScope @Inject lateinit var globalScope: CoroutineScope + @Inject lateinit var notificationUtils: NotificationUtils + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + showStickyNotification() + startPollingActiveSession() + return START_STICKY + } + + private fun startPollingActiveSession() { + globalScope.launch { + do { + delay(1.seconds.inWholeMilliseconds) + } while (activeSessionHolder.hasActiveSession().not()) + myStopSelf() + } + } + + private fun showStickyNotification() { + val notificationId = Random.nextInt() + val notification = notificationUtils.buildStartAppNotification() + startForeground(notificationId, notification) + } +} From 3063c0da496499ad93cd0b4bbbbe0e2d66723e5c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Jul 2022 09:36:30 +0200 Subject: [PATCH 046/133] Avoid updating the UI every seconds --- .../im/vector/app/features/MainActivity.kt | 18 +++++++++++------- .../app/features/start/StartAppViewEvent.kt | 8 ++++++++ .../app/features/start/StartAppViewModel.kt | 19 ++++++++----------- .../app/features/start/StartAppViewState.kt | 2 +- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 041ee44c1c..61127e2c82 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -142,23 +142,27 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } private fun renderState(state: StartAppViewState) { - if (state.duration > 0) { + if (state.mayBeLongToProcess) { views.status.setText(R.string.updating_your_data) } - views.status.isVisible = state.duration > 0 - if (state.duration == 1L && startAppViewModel.shouldStartApp()) { - // Start foreground service, because the operation may take a while - val intent = Intent(this, StartAppAndroidService::class.java) - ContextCompat.startForegroundService(this, intent) - } + views.status.isVisible = state.mayBeLongToProcess } private fun handleViewEvents(event: StartAppViewEvent) { when (event) { + StartAppViewEvent.StartForegroundService -> handleStartForegroundService() StartAppViewEvent.AppStarted -> handleAppStarted() } } + private fun handleStartForegroundService() { + if (startAppViewModel.shouldStartApp()) { + // Start foreground service, because the operation may take a while + val intent = Intent(this, StartAppAndroidService::class.java) + ContextCompat.startForegroundService(this, intent) + } + } + private fun handleAppStarted() { if (intent.hasExtra(EXTRA_NEXT_INTENT)) { // Start the next Activity diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt index 9e185cbe04..986d41f983 100644 --- a/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt @@ -19,5 +19,13 @@ package im.vector.app.features.start import im.vector.app.core.platform.VectorViewEvents sealed interface StartAppViewEvent : VectorViewEvents { + /** + * Will be sent if the process is taking more than 1 second. + */ + object StartForegroundService : StartAppViewEvent + + /** + * Will be sent when the current Session has been set. + */ object AppStarted : StartAppViewEvent } diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt index b66a072195..62a7517f5a 100644 --- a/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt @@ -24,10 +24,8 @@ import im.vector.app.core.di.ActiveSessionSetter import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.lib.core.utils.flow.tickerFlow import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.seconds @@ -54,7 +52,7 @@ class StartAppViewModel @AssistedInject constructor( } private fun handleStartApp() { - startTimer() + handleLongProcessing() viewModelScope.launch(Dispatchers.IO) { // This can take time because of DB migration(s), so do it in a background task. activeSessionSetter.tryToSetActiveSession(startSync = true) @@ -62,12 +60,11 @@ class StartAppViewModel @AssistedInject constructor( } } - private fun startTimer() { - setState { copy(duration = 0) } - tickerFlow(viewModelScope, 1.seconds.inWholeMilliseconds) - .onEach { - setState { copy(duration = duration + 1) } - } - .launchIn(viewModelScope) + private fun handleLongProcessing() { + viewModelScope.launch(Dispatchers.Default) { + delay(1.seconds.inWholeMilliseconds) + setState { copy(mayBeLongToProcess = true) } + _viewEvents.post(StartAppViewEvent.StartForegroundService) + } } } diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt index 50418e90dc..3ff933f054 100644 --- a/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt @@ -19,5 +19,5 @@ package im.vector.app.features.start import com.airbnb.mvrx.MavericksState data class StartAppViewState( - val duration: Long = 0 + val mayBeLongToProcess: Boolean = false ) : MavericksState From b83f6f2cb7a532c49190448fd811d783bff0ebe9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 Jul 2022 10:30:18 +0200 Subject: [PATCH 047/133] Changelog --- changelog.d/6548.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6548.feature diff --git a/changelog.d/6548.feature b/changelog.d/6548.feature new file mode 100644 index 0000000000..8c40a37063 --- /dev/null +++ b/changelog.d/6548.feature @@ -0,0 +1 @@ +Move initialization of the Session to a background thread. MainActivity is restoring the session now, instead of VectorApplication. Useful when for instance a long migration of a database is required. From 125135c2505479a637658a13d8b1474d644cf997 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 4 Jul 2022 13:53:10 +0300 Subject: [PATCH 048/133] Add element call widget type. --- .../android/sdk/api/session/widgets/model/WidgetType.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt index ee098f9bf2..24f3a155ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt @@ -28,7 +28,8 @@ private val DEFINED_TYPES by lazy { WidgetType.StickerPicker, WidgetType.Grafana, WidgetType.Custom, - WidgetType.IntegrationManager + WidgetType.IntegrationManager, + WidgetType.ElementCall ) } @@ -47,6 +48,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr object Grafana : WidgetType("m.grafana") object Custom : WidgetType("m.custom") object IntegrationManager : WidgetType("m.integration_manager") + object ElementCall : WidgetType("io.element.call") data class Fallback(override val preferred: String) : WidgetType(preferred) fun matches(type: String): Boolean { From 61f05e78a3add57c3dd3ddbcd589517aa7411d74 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 4 Jul 2022 17:35:07 +0300 Subject: [PATCH 049/133] Create custom widget args for element call. --- .../im/vector/app/features/navigation/DefaultNavigator.kt | 3 +++ .../im/vector/app/features/widgets/WidgetArgsBuilder.kt | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 291eee307f..38edaa27e2 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -465,6 +465,9 @@ class DefaultNavigator @Inject constructor( val enableVideo = options?.get(JitsiCallViewModel.ENABLE_VIDEO_OPTION) == true context.startActivity(VectorJitsiActivity.newIntent(context, roomId = roomId, widgetId = widget.widgetId, enableVideo = enableVideo)) } + } else if (widget.type is WidgetType.ElementCall) { + val widgetArgs = widgetArgsBuilder.buildElementCallWidgetArgs(roomId, widget) + context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) } else { val widgetArgs = widgetArgsBuilder.buildRoomWidgetArgs(roomId, widget) context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetArgsBuilder.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetArgsBuilder.kt index 777bd9cc7e..83ea100cb6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetArgsBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetArgsBuilder.kt @@ -78,6 +78,13 @@ class WidgetArgsBuilder @Inject constructor( ) } + fun buildElementCallWidgetArgs(roomId: String, widget: Widget): WidgetArgs { + return buildRoomWidgetArgs(roomId, widget) + .copy( + kind = WidgetKind.ELEMENT_CALL + ) + } + @Suppress("UNCHECKED_CAST") private fun Map.filterNotNull(): Map { return filterValues { it != null } as Map From b3a8052a519704a4a2fb5616ed218f0b02aed8c1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 12:30:55 +0300 Subject: [PATCH 050/133] Add element call widget type. --- .../java/im/vector/app/features/widgets/WidgetViewState.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt index 2d98f734dd..7619fea766 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt @@ -33,7 +33,8 @@ enum class WidgetStatus { enum class WidgetKind(@StringRes val nameRes: Int, val screenId: String?) { ROOM(R.string.room_widget_activity_title, null), STICKER_PICKER(R.string.title_activity_choose_sticker, WidgetType.StickerPicker.preferred), - INTEGRATION_MANAGER(0, null); + INTEGRATION_MANAGER(0, null), + ELEMENT_CALL(0, null); fun isAdmin(): Boolean { return this == STICKER_PICKER || this == INTEGRATION_MANAGER From 85aba894d6849b0416cb71e5b9012ec001a572f4 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 5 Jul 2022 16:06:20 +0300 Subject: [PATCH 051/133] Support picture-in-picture mode for element call widget. --- vector/src/main/AndroidManifest.xml | 3 ++- .../app/features/widgets/WidgetActivity.kt | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 1c104f3bbf..ca61e077fb 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -308,7 +308,8 @@ + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:supportsPictureInPicture="true" /> diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 954f622801..dc756ec7d7 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -17,8 +17,11 @@ package im.vector.app.features.widgets import android.app.Activity +import android.app.PictureInPictureParams import android.content.Context import android.content.Intent +import android.os.Build +import android.util.Rational import androidx.core.view.isVisible import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel @@ -119,6 +122,24 @@ class WidgetActivity : VectorBaseActivity() { } } + override fun onUserLeaveHint() { + super.onUserLeaveHint() + val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(Mavericks.KEY_ARG) + if (widgetArgs?.kind == WidgetKind.ELEMENT_CALL) { + enterPictureInPicture() + } + } + + private fun enterPictureInPicture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val aspectRatio = Rational(resources.getDimensionPixelSize(R.dimen.call_pip_width), resources.getDimensionPixelSize(R.dimen.call_pip_height)) + val params = PictureInPictureParams.Builder() + .setAspectRatio(aspectRatio) + .build() + enterPictureInPictureMode(params) + } + } + private fun handleClose(event: WidgetViewEvents.Close) { if (event.content != null) { val intent = createResultIntent(event.content) From a0eb2e733c2b4e8a861ce7818c6ee1be869bce3e Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 6 Jul 2022 14:54:24 +0300 Subject: [PATCH 052/133] Skip widget permissions for element call. --- .../app/features/widgets/WidgetActivity.kt | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index dc756ec7d7..399c2d392b 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -85,29 +85,36 @@ class WidgetActivity : VectorBaseActivity() { } } - permissionViewModel.observeViewEvents { - when (it) { - is RoomWidgetPermissionViewEvents.Close -> finish() + // Trust element call widget by default + if (widgetArgs.kind == WidgetKind.ELEMENT_CALL) { + if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { + addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) + } + } else { + permissionViewModel.observeViewEvents { + when (it) { + is RoomWidgetPermissionViewEvents.Close -> finish() + } } - } - viewModel.onEach(WidgetViewState::status) { ws -> - when (ws) { - WidgetStatus.UNKNOWN -> { - } - WidgetStatus.WIDGET_NOT_ALLOWED -> { - val dFrag = supportFragmentManager.findFragmentByTag(WIDGET_PERMISSION_FRAGMENT_TAG) as? RoomWidgetPermissionBottomSheet - if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { - return@onEach - } else { - RoomWidgetPermissionBottomSheet - .newInstance(widgetArgs) - .show(supportFragmentManager, WIDGET_PERMISSION_FRAGMENT_TAG) + viewModel.onEach(WidgetViewState::status) { ws -> + when (ws) { + WidgetStatus.UNKNOWN -> { } - } - WidgetStatus.WIDGET_ALLOWED -> { - if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { - addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) + WidgetStatus.WIDGET_NOT_ALLOWED -> { + val dFrag = supportFragmentManager.findFragmentByTag(WIDGET_PERMISSION_FRAGMENT_TAG) as? RoomWidgetPermissionBottomSheet + if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { + return@onEach + } else { + RoomWidgetPermissionBottomSheet + .newInstance(widgetArgs) + .show(supportFragmentManager, WIDGET_PERMISSION_FRAGMENT_TAG) + } + } + WidgetStatus.WIDGET_ALLOWED -> { + if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { + addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) + } } } } From f5ec7a312fcfd451538acd5bed1d71795477512e Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 6 Jul 2022 15:22:28 +0300 Subject: [PATCH 053/133] Auto grant WebView permissions if they are already granted system level. --- .../vector/app/core/utils/PermissionsTools.kt | 27 +++++++++++++++++++ .../app/features/widgets/WidgetFragment.kt | 2 +- .../features/widgets/webview/WidgetWebView.kt | 10 +++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 9ad95d3c55..051a6cd8ce 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -19,6 +19,7 @@ package im.vector.app.core.utils import android.Manifest import android.app.Activity import android.content.pm.PackageManager +import android.webkit.PermissionRequest import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -137,6 +138,32 @@ fun checkPermissions( } } +/** + * Checks if required WebView permissions are already granted system level. + * @param activity the calling Activity that is requesting the permissions (or fragment parent) + * @param request WebView permission request of onPermissionRequest function + * @return true if WebView permissions are already granted, false otherwise + */ +fun checkWebViewPermissions(activity: Activity, request: PermissionRequest): Boolean { + return request.resources.all { + when (it) { + PermissionRequest.RESOURCE_AUDIO_CAPTURE -> { + PERMISSIONS_FOR_AUDIO_IP_CALL.all { permission -> + ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED + } + } + PermissionRequest.RESOURCE_VIDEO_CAPTURE -> { + PERMISSIONS_FOR_VIDEO_IP_CALL.all { permission -> + ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED + } + } + else -> { + false + } + } + } +} + /** * To be call after the permission request. * diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index 5501031e92..a85e45b074 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -81,7 +81,7 @@ class WidgetFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.widgetWebView.setupForWidget(this) + views.widgetWebView.setupForWidget(requireActivity(), this) if (fragmentArgs.kind.isAdmin()) { viewModel.getPostAPIMediator().setWebView(views.widgetWebView) } diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index 0207987ca3..505645a668 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -17,18 +17,20 @@ package im.vector.app.features.widgets.webview import android.annotation.SuppressLint +import android.app.Activity import android.view.ViewGroup import android.webkit.CookieManager import android.webkit.PermissionRequest import android.webkit.WebChromeClient import android.webkit.WebView import im.vector.app.R +import im.vector.app.core.utils.checkWebViewPermissions import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.webview.VectorWebViewClient import im.vector.app.features.webview.WebEventListener @SuppressLint("NewApi") -fun WebView.setupForWidget(eventListener: WebEventListener) { +fun WebView.setupForWidget(activity: Activity, eventListener: WebEventListener) { // xml value seems ignored setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorSurface)) @@ -59,7 +61,11 @@ fun WebView.setupForWidget(eventListener: WebEventListener) { // Permission requests webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { - eventListener.onPermissionRequest(request) + if (checkWebViewPermissions(activity, request)) { + request.grant(request.resources) + } else { + eventListener.onPermissionRequest(request) + } } } webViewClient = VectorWebViewClient(eventListener) From 9f3c8b6e5f80c2adc0e01f08734e2e600069d39b Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 5 Jul 2022 17:46:29 +0200 Subject: [PATCH 054/133] Handle case when device cannot be verified --- changelog.d/6466.bugfix | 1 + .../java/im/vector/app/CantVerifyTest.kt | 75 +++++++++++++++++++ .../vector/app/features/home/HomeActivity.kt | 16 +++- .../features/home/HomeActivityViewEvents.kt | 8 +- .../features/home/HomeActivityViewModel.kt | 32 ++++++-- ...ceVerificationInfoBottomSheetController.kt | 64 ++++++++++++---- .../settings/devices/DevicesAction.kt | 1 + .../settings/devices/DevicesViewEvents.kt | 2 + .../settings/devices/DevicesViewModel.kt | 1 + .../devices/VectorSettingsDevicesFragment.kt | 4 + vector/src/main/res/values/strings.xml | 4 +- 11 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 changelog.d/6466.bugfix create mode 100644 vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt diff --git a/changelog.d/6466.bugfix b/changelog.d/6466.bugfix new file mode 100644 index 0000000000..31fef9f69d --- /dev/null +++ b/changelog.d/6466.bugfix @@ -0,0 +1 @@ +When there is no way to verify a device (no 4S nor other device) propose to reset verification keys diff --git a/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt b/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt new file mode 100644 index 0000000000..ba844e56b7 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 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 + +import android.view.View +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import im.vector.app.features.MainActivity +import im.vector.app.ui.robot.ElementRobot +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.UUID + +@RunWith(AndroidJUnit4::class) +@LargeTest +class CantVerifyTest : VerificationTestBase() { + + @get:Rule + val activityRule = ActivityScenarioRule(MainActivity::class.java) + + private val elementRobot = ElementRobot() + var userName: String = "loginTest_${UUID.randomUUID()}" + + @Test + fun checkCantVerifyPopup() { + // Let' create an account + // This first session will create cross signing keys then logout + elementRobot.signUp(userName) + Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000)) + + elementRobot.signout(false) + Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000)) + + // Let's login again now + // There are no methods to verify (no other devices, nor 4S) + // So it should ask to reset all + elementRobot.login(userName) + + val activity = EspressoHelper.getCurrentActivity()!! + Espresso.onView(ViewMatchers.isRoot()) + .perform(waitForView(ViewMatchers.withText(R.string.crosssigning_cannot_verify_this_session))) + + // check that the text is correct + val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground)!! + activity.runOnUiThread { popup.performClick() } + + // ensure that it's the 4S setup bottomsheet + Espresso.onView(ViewMatchers.withId(R.id.bottomSheetFragmentContainer)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + + Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000)) + + Espresso.onView(ViewMatchers.withText(R.string.bottom_sheet_setup_secure_backup_title)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 926c1eb113..655a815506 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -238,7 +238,8 @@ class HomeActivity : homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + is HomeActivityViewEvents.CurrentSessionNotVerified -> handleOnNewSession(it) + is HomeActivityViewEvents.CurrentSessionCannotBeVerified -> handleCantVerify(it) HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() HomeActivityViewEvents.StartRecoverySetupFlow -> handleStartRecoverySetup() is HomeActivityViewEvents.ForceVerification -> { @@ -422,7 +423,7 @@ class HomeActivity : } } - private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) { + private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) { // We need to ask promptSecurityEvent( event.userItem, @@ -437,6 +438,17 @@ class HomeActivity : } } + private fun handleCantVerify(event: HomeActivityViewEvents.CurrentSessionCannotBeVerified) { + // We need to ask + promptSecurityEvent( + event.userItem, + R.string.crosssigning_cannot_verify_this_session, + R.string.crosssigning_cannot_verify_this_session_desc + ) { + it.navigator.open4SSetup(it, SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET) + } + } + private fun handlePromptToEnablePush() { popupAlertManager.postVectorAlert( DefaultVectorAlert( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index cb31a568e4..170550d5b4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -21,7 +21,13 @@ import org.matrix.android.sdk.api.util.MatrixItem sealed interface HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents - data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents + data class CurrentSessionNotVerified( + val userItem: MatrixItem.UserItem?, + val waitForIncomingRequest: Boolean = true, + ) : HomeActivityViewEvents + data class CurrentSessionCannotBeVerified( + val userItem: MatrixItem.UserItem?, + ) : HomeActivityViewEvents data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index b45e6fbcb0..7a95a70c3d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -364,14 +364,30 @@ class HomeActivityViewModel @AssistedInject constructor( // If 4S is forced, force verification _viewEvents.post(HomeActivityViewEvents.ForceVerification(true)) } else { - // New session - _viewEvents.post( - HomeActivityViewEvents.OnNewSession( - session.getUser(session.myUserId)?.toMatrixItem(), - // Always send request instead of waiting for an incoming as per recent EW changes - false - ) - ) + // we wan't to check if there is a way to actually verify this session, + // that means that there is another session to verify against, or + // secure backup is setup + val hasTargetDeviceToVerifyAgainst = session + .cryptoService() + .getUserDevices(session.myUserId) + .size >= 2 // this one + another + val is4Ssetup = session.sharedSecretStorageService().isRecoverySetup() + if (hasTargetDeviceToVerifyAgainst || is4Ssetup) { + // New session + _viewEvents.post( + HomeActivityViewEvents.CurrentSessionNotVerified( + session.getUser(session.myUserId)?.toMatrixItem(), + // Always send request instead of waiting for an incoming as per recent EW changes + false + ) + ) + } else { + _viewEvents.post( + HomeActivityViewEvents.CurrentSessionCannotBeVerified( + session.getUser(session.myUserId)?.toMatrixItem(), + ) + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index 85abf846fa..e4a147fc10 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -106,6 +106,18 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( .toEpoxyCharSequence() ) } + } else { + genericItem { + id("reset${cryptoDeviceInfo.deviceId}") + style(ItemStyle.BIG_TEXT) + titleIconResourceId(shield) + title(host.stringProvider.getString(R.string.crosssigning_cannot_verify_this_session).toEpoxyCharSequence()) + description( + host.stringProvider + .getString(R.string.crosssigning_cannot_verify_this_session_desc) + .toEpoxyCharSequence() + ) + } } } else { if (!currentSessionIsTrusted) { @@ -141,19 +153,45 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( description("(${cryptoDeviceInfo.deviceId})".toEpoxyCharSequence()) } - if (isMine && !currentSessionIsTrusted && data.canVerifySession) { - // Add complete security - bottomSheetDividerItem { - id("completeSecurityDiv") - } - bottomSheetVerificationActionItem { - id("completeSecurity") - title(host.stringProvider.getString(R.string.crosssigning_verify_this_session)) - titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - iconRes(R.drawable.ic_arrow_right) - iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - listener { - host.callback?.onAction(DevicesAction.CompleteSecurity) + if (isMine) { + if (!currentSessionIsTrusted) { + if (data.canVerifySession) { + // Add complete security + bottomSheetDividerItem { + id("completeSecurityDiv") + } + bottomSheetVerificationActionItem { + id("completeSecurity") + title(host.stringProvider.getString(R.string.crosssigning_verify_this_session)) + titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + iconRes(R.drawable.ic_arrow_right) + iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + listener { + host.callback?.onAction(DevicesAction.CompleteSecurity) + } + } + } else { + bottomSheetDividerItem { + id("resetSecurityDiv") + } + bottomSheetVerificationActionItem { + id("resetSecurity") + title(host.stringProvider.getString(R.string.secure_backup_reset_all)) + titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + iconRes(R.drawable.ic_arrow_right) + iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + listener { + host.callback?.onAction(DevicesAction.ResetSecurity) + } + } + } + } else if (!isMine) { + if (currentSessionIsTrusted) { + // we can propose to verify it + val isVerified = cryptoDeviceInfo.trustLevel?.crossSigningVerified.orFalse() + if (!isVerified) { + addVerifyActions(cryptoDeviceInfo) + } } } } else if (!isMine) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt index 8ee0e7636e..b915236329 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt @@ -30,6 +30,7 @@ sealed class DevicesAction : VectorViewModelAction { data class VerifyMyDevice(val deviceId: String) : DevicesAction() data class VerifyMyDeviceManually(val deviceId: String) : DevicesAction() object CompleteSecurity : DevicesAction() + object ResetSecurity : DevicesAction() data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction() object SsoAuthDone : DevicesAction() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt index c057e2b565..c97f353110 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt @@ -48,4 +48,6 @@ sealed class DevicesViewEvents : VectorViewEvents { ) : DevicesViewEvents() data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents() + + object PromptResetSecrets : DevicesViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 1d9f2a97c2..1840a97098 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -239,6 +239,7 @@ class DevicesViewModel @AssistedInject constructor( uiaContinuation = null pendingAuth = null } + DevicesAction.ResetSecurity -> _viewEvents.post(DevicesViewEvents.PromptResetSecrets) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index 0c52099f92..a132dc1f49 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.features.auth.ReAuthActivity +import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.VerificationBottomSheet import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo @@ -89,6 +90,9 @@ class VectorSettingsDevicesFragment @Inject constructor( viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo)) } } + is DevicesViewEvents.PromptResetSecrets -> { + navigator.open4SSetup(requireContext(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET) + } } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ed0cf691e2..062b7b4484 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2351,7 +2351,9 @@ %d active sessions - Verify this login + Verify this device + Unable to verify this device + You won\'t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh. Use an existing session to verify this one, granting it access to encrypted messages. From 6657d6c5afc4e8eb3c49e3cf6b6ca437ffc3b4e9 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 5 Jul 2022 17:54:11 +0200 Subject: [PATCH 055/133] clean --- .../devices/DeviceVerificationInfoBottomSheetController.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index e4a147fc10..b1084077cb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -194,7 +194,9 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( } } } - } else if (!isMine) { + } else + /** if (!isMine) **/ + { if (currentSessionIsTrusted) { // we can propose to verify it val isVerified = cryptoDeviceInfo.trustLevel?.crossSigningVerified.orFalse() From ac7b47b8b2d37d6f76c4aada806e37ef25ce6b06 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jul 2022 10:17:41 +0200 Subject: [PATCH 056/133] code reviews --- .../DeviceVerificationInfoBottomSheetController.kt | 8 -------- vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index b1084077cb..c91c0f457b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -185,14 +185,6 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( } } } - } else if (!isMine) { - if (currentSessionIsTrusted) { - // we can propose to verify it - val isVerified = cryptoDeviceInfo.trustLevel?.crossSigningVerified.orFalse() - if (!isVerified) { - addVerifyActions(cryptoDeviceInfo) - } - } } } else /** if (!isMine) **/ diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 062b7b4484..d203b74e52 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2353,7 +2353,7 @@ Verify this device Unable to verify this device - You won\'t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh. + You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh. Use an existing session to verify this one, granting it access to encrypted messages. From fd3b082a2c5bc55ebcfc7dcc3e6ac780e9d7aeac Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 13:13:14 +0300 Subject: [PATCH 057/133] Open element call widget. --- .../features/home/room/detail/RoomDetailViewEvents.kt | 1 + .../features/home/room/detail/RoomDetailViewState.kt | 2 ++ .../app/features/home/room/detail/TimelineFragment.kt | 10 ++++++++++ .../app/features/widgets/webview/WidgetWebView.kt | 2 ++ 4 files changed, 15 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index dcfee2d919..3af849e965 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -84,4 +84,5 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents() object StopChatEffects : RoomDetailViewEvents() object RoomReplacementStarted : RoomDetailViewEvents() + object OpenElementCallWidget : RoomDetailViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 8500d1ed96..7aa7d5a877 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -102,6 +102,8 @@ data class RoomDetailViewState( // It can differs for a short period of time on the JitsiState as its computed async. fun hasActiveJitsiWidget() = activeRoomWidgets()?.any { it.type == WidgetType.Jitsi && it.isActive }.orFalse() + fun hasActiveElementCallWidget() = activeRoomWidgets()?.any { it.type == WidgetType.ElementCall && it.isActive }.orFalse() + fun isDm() = asyncRoomSummary()?.isDirect == true fun isThreadTimeline() = rootThreadEventId != null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 31c1004ef9..1d77e4c83a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -498,6 +498,7 @@ class TimelineFragment @Inject constructor( RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement() + RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget() } } @@ -2653,6 +2654,15 @@ class TimelineFragment @Inject constructor( .show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET") } + private fun handleOpenElementCallWidget() = withState(timelineViewModel) { state -> + state + .activeRoomWidgets() + ?.find { it.type == WidgetType.ElementCall } + ?.also { widget -> + navigator.openRoomWidget(requireContext(), state.roomId, widget) + } + } + override fun onTapToReturnToCall() { callManager.getCurrentCall()?.let { call -> VectorCallActivity.newIntent( diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index 505645a668..2f3b449b1a 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -58,6 +58,8 @@ fun WebView.setupForWidget(activity: Activity, eventListener: WebEventListener) settings.displayZoomControls = false + settings.mediaPlaybackRequiresUserGesture = false + // Permission requests webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { From 5c55263cf69fcbb1c25b1bad51f4094bd017e3ce Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Thu, 7 Jul 2022 08:46:47 +0200 Subject: [PATCH 058/133] Suppress webview / checkbox permission dialog Signed-off-by: Johannes Marbach --- .../app/features/widgets/WidgetFragment.kt | 3 +- .../widgets/webview/WebviewPermissionUtils.kt | 49 +++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index a85e45b074..a34e06aace 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -298,7 +298,8 @@ class WidgetFragment @Inject constructor( request = request, context = requireContext(), activity = requireActivity(), - activityResultLauncher = permissionResultLauncher + activityResultLauncher = permissionResultLauncher, + autoApprove = fragmentArgs.kind == WidgetKind.ELEMENT_CALL ) } diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WebviewPermissionUtils.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WebviewPermissionUtils.kt index fa7b842ab9..44af4ec335 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WebviewPermissionUtils.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WebviewPermissionUtils.kt @@ -41,11 +41,22 @@ class WebviewPermissionUtils @Inject constructor( request: PermissionRequest, context: Context, activity: FragmentActivity, - activityResultLauncher: ActivityResultLauncher> + activityResultLauncher: ActivityResultLauncher>, + autoApprove: Boolean = false ) { + if (autoApprove) { + onPermissionsSelected( + permissions = request.resources.toList(), + request = request, + activity = activity, + activityResultLauncher = activityResultLauncher) + return + } + val allowedPermissions = request.resources.map { it to false }.toMutableList() + MaterialAlertDialogBuilder(context) .setTitle(title) .setMultiChoiceItems( @@ -54,21 +65,10 @@ class WebviewPermissionUtils @Inject constructor( allowedPermissions[which] = allowedPermissions[which].first to isChecked } .setPositiveButton(R.string.room_widget_resource_grant_permission) { _, _ -> - permissionRequest = request - selectedPermissions = allowedPermissions.mapNotNull { perm -> + val permissions = allowedPermissions.mapNotNull { perm -> perm.first.takeIf { perm.second } } - - val requiredAndroidPermissions = selectedPermissions.mapNotNull { permission -> - webPermissionToAndroidPermission(permission) - } - - // When checkPermissions returns false, some of the required Android permissions will - // have to be requested and the flow completes asynchronously via onPermissionResult - if (checkPermissions(requiredAndroidPermissions, activity, activityResultLauncher)) { - request.grant(selectedPermissions.toTypedArray()) - reset() - } + onPermissionsSelected(permissions, request, activity, activityResultLauncher) } .setNegativeButton(R.string.room_widget_resource_decline_permission) { _, _ -> request.deny() @@ -76,6 +76,27 @@ class WebviewPermissionUtils @Inject constructor( .show() } + private fun onPermissionsSelected( + permissions: List, + request: PermissionRequest, + activity: FragmentActivity, + activityResultLauncher: ActivityResultLauncher>, + ) { + permissionRequest = request + selectedPermissions = permissions + + val requiredAndroidPermissions = selectedPermissions.mapNotNull { permission -> + webPermissionToAndroidPermission(permission) + } + + // When checkPermissions returns false, some of the required Android permissions will + // have to be requested and the flow completes asynchronously via onPermissionResult + if (checkPermissions(requiredAndroidPermissions, activity, activityResultLauncher)) { + request.grant(selectedPermissions.toTypedArray()) + reset() + } + } + fun onPermissionResult(result: Map) { if (permissionRequest == null) { fatalError( From 95783506a243f199ce4f964852e0b00e288b1b8a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 7 Jul 2022 13:19:40 +0300 Subject: [PATCH 059/133] Stop javascript for non element call widgets. --- .../java/im/vector/app/features/widgets/WidgetFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index a34e06aace..390bd3c88b 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -131,9 +131,11 @@ class WidgetFragment @Inject constructor( override fun onPause() { super.onPause() - views.widgetWebView.let { - it.pauseTimers() - it.onPause() + if (fragmentArgs.kind != WidgetKind.ELEMENT_CALL) { + views.widgetWebView.let { + it.pauseTimers() + it.onPause() + } } } From 85b5713b244775f535c37e2274836d5b4e9fcf7a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 8 Jul 2022 15:06:44 +0300 Subject: [PATCH 060/133] Add a hangup button in pip mode. --- .../app/features/widgets/WidgetAction.kt | 1 + .../app/features/widgets/WidgetActivity.kt | 54 +++++++++++++++++-- .../app/features/widgets/WidgetViewModel.kt | 5 ++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt index b72ea68b7f..f525af6109 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt @@ -26,4 +26,5 @@ sealed class WidgetAction : VectorViewModelAction { object DeleteWidget : WidgetAction() object RevokeWidget : WidgetAction() object OnTermsReviewed : WidgetAction() + object HangupElementCall : WidgetAction() } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 399c2d392b..dc1bc2c4ff 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -17,11 +17,18 @@ package im.vector.app.features.widgets import android.app.Activity +import android.app.PendingIntent import android.app.PictureInPictureParams +import android.app.RemoteAction +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter +import android.content.res.Configuration +import android.graphics.drawable.Icon import android.os.Build import android.util.Rational +import androidx.annotation.RequiresApi import androidx.core.view.isVisible import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel @@ -43,6 +50,10 @@ class WidgetActivity : VectorBaseActivity() { private const val WIDGET_FRAGMENT_TAG = "WIDGET_FRAGMENT_TAG" private const val WIDGET_PERMISSION_FRAGMENT_TAG = "WIDGET_PERMISSION_FRAGMENT_TAG" private const val EXTRA_RESULT = "EXTRA_RESULT" + private const val REQUEST_CODE_HANGUP = 1 + private const val ACTION_MEDIA_CONTROL = "MEDIA_CONTROL" + private const val EXTRA_CONTROL_TYPE = "EXTRA_CONTROL_TYPE" + private const val CONTROL_TYPE_HANGUP = 2 fun newIntent(context: Context, args: WidgetArgs): Intent { return Intent(context, WidgetActivity::class.java).apply { @@ -139,11 +150,44 @@ class WidgetActivity : VectorBaseActivity() { private fun enterPictureInPicture() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val aspectRatio = Rational(resources.getDimensionPixelSize(R.dimen.call_pip_width), resources.getDimensionPixelSize(R.dimen.call_pip_height)) - val params = PictureInPictureParams.Builder() - .setAspectRatio(aspectRatio) - .build() - enterPictureInPictureMode(params) + createElementCallPipParams()?.let { + enterPictureInPictureMode(it) + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createElementCallPipParams(): PictureInPictureParams? { + val actions = mutableListOf() + val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, CONTROL_TYPE_HANGUP) + val pendingIntent = PendingIntent.getBroadcast(this, REQUEST_CODE_HANGUP, intent, 0) + val icon = Icon.createWithResource(this, R.drawable.ic_call_hangup) + actions.add(RemoteAction(icon, getString(R.string.call_notification_hangup), getString(R.string.call_notification_hangup), pendingIntent)) + + val aspectRatio = Rational(resources.getDimensionPixelSize(R.dimen.call_pip_width), resources.getDimensionPixelSize(R.dimen.call_pip_height)) + return PictureInPictureParams.Builder() + .setAspectRatio(aspectRatio) + .setActions(actions) + .build() + } + + private var hangupBroadcastReceiver: BroadcastReceiver? = null + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + if (isInPictureInPictureMode) { + hangupBroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == ACTION_MEDIA_CONTROL) { + val controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0) + if (controlType == CONTROL_TYPE_HANGUP) { + viewModel.handle(WidgetAction.HangupElementCall) + } + } + } + } + registerReceiver(hangupBroadcastReceiver, IntentFilter(ACTION_MEDIA_CONTROL)) + } else { + unregisterReceiver(hangupBroadcastReceiver) } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index b3f4712815..17dc244f4c 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -147,9 +147,14 @@ class WidgetViewModel @AssistedInject constructor( WidgetAction.DeleteWidget -> handleDeleteWidget() WidgetAction.RevokeWidget -> handleRevokeWidget() WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) + WidgetAction.HangupElementCall -> handleHangupElementCall() } } + private fun handleHangupElementCall() { + _viewEvents.post(WidgetViewEvents.Close()) + } + private fun handleRevokeWidget() { viewModelScope.launch { val widgetId = initialState.widgetId ?: return@launch From 7dfe5264cde3cf5669520d980f105057afcf77a1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 15:03:44 +0300 Subject: [PATCH 061/133] Fix picture in picture mode. --- .../vector/app/features/widgets/WidgetActivity.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index dc1bc2c4ff..90f1ab4bbb 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -29,9 +29,12 @@ import android.graphics.drawable.Icon import android.os.Build import android.util.Rational import androidx.annotation.RequiresApi +import androidx.core.app.PictureInPictureModeChangedInfo +import androidx.core.util.Consumer import androidx.core.view.isVisible import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel +import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.addFragment @@ -99,6 +102,7 @@ class WidgetActivity : VectorBaseActivity() { // Trust element call widget by default if (widgetArgs.kind == WidgetKind.ELEMENT_CALL) { if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { + addOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer) addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) } } else { @@ -148,6 +152,11 @@ class WidgetActivity : VectorBaseActivity() { } } + override fun onDestroy() { + removeOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer) + super.onDestroy() + } + private fun enterPictureInPicture() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createElementCallPipParams()?.let { @@ -172,8 +181,10 @@ class WidgetActivity : VectorBaseActivity() { } private var hangupBroadcastReceiver: BroadcastReceiver? = null - override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) { - super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + + private val pictureInPictureModeChangedInfoConsumer = Consumer { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return@Consumer + if (isInPictureInPictureMode) { hangupBroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { From 9a143d99cbc34ddd1e53aaa6d43f088c7c52d6ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 Jul 2022 14:24:52 +0200 Subject: [PATCH 062/133] Update issue triage workflow. --- .github/workflows/triage-labelled.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index d2aa72308d..5cecc2e2aa 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -248,9 +248,12 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - (contains(github.event.issue.labels.*.name, 'Z-ElementX-Alpha') || - contains(github.event.issue.labels.*.name, 'Z-ElementX-Beta') || - contains(github.event.issue.labels.*.name, 'Z-ElementX')) + (contains(github.event.issue.labels.*.name, 'Z-BBQ-Alpha') || + contains(github.event.issue.labels.*.name, 'Z-BBQ-Beta') || + contains(github.event.issue.labels.*.name, 'Z-BBQ-Release' || + contains(github.event.issue.labels.*.name, 'Z-Banquet-Alpha') || + contains(github.event.issue.labels.*.name, 'Z-Banquet-Beta') || + contains(github.event.issue.labels.*.name, 'Z-Banquet-Release')) steps: - uses: octokit/graphql-action@v2.x with: From 0d4697b7e1b7e7581308df4b67b76fe552a24573 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 15:55:51 +0300 Subject: [PATCH 063/133] Show call icon if there is an active element call widget. --- .../app/features/home/room/detail/RoomDetailViewState.kt | 3 ++- .../vector/app/features/home/room/detail/TimelineFragment.kt | 4 ++-- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 7aa7d5a877..b47b46c152 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -93,7 +93,8 @@ data class RoomDetailViewState( return asyncRoomSummary.invoke()?.isDirect ?: true || // When there is only one member, a warning will be displayed when the user // clicks on the menu item to start a call - asyncRoomSummary.invoke()?.joinedMembersCount == 1 + asyncRoomSummary.invoke()?.joinedMembersCount == 1 || + hasActiveElementCallWidget() } fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 1d77e4c83a..27ba12e4d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1088,8 +1088,8 @@ class TimelineFragment @Inject constructor( val hasCallInRoom = callManager.getCallsByRoomId(state.roomId).isNotEmpty() || state.jitsiState.hasJoined val callButtonsEnabled = !hasCallInRoom && when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { 1 -> false - 2 -> state.isAllowedToStartWebRTCCall - else -> state.isAllowedToManageWidgets + 2 -> state.isAllowedToStartWebRTCCall || state.hasActiveElementCallWidget() + else -> state.isAllowedToManageWidgets || state.hasActiveElementCallWidget() } setOf(R.id.voice_call, R.id.video_call).forEach { menu.findItem(it).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index e3ea8a0826..71b6b22aed 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -752,7 +752,7 @@ class TimelineViewModel @AssistedInject constructor( R.id.timeline_setting -> true R.id.invite -> state.canInvite R.id.open_matrix_apps -> true - R.id.voice_call -> state.isCallOptionAvailable() + R.id.voice_call -> state.isCallOptionAvailable() || state.hasActiveElementCallWidget() R.id.video_call -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ R.id.join_conference -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined From da780ac56f2720a547bb5e9872b514b4aad3fae0 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 16:26:10 +0300 Subject: [PATCH 064/133] Open element call widget. --- .../app/features/home/room/detail/RoomDetailAction.kt | 2 ++ .../app/features/home/room/detail/RoomDetailViewState.kt | 3 +-- .../features/home/room/detail/StartCallActionsHandler.kt | 5 +++++ .../app/features/home/room/detail/TimelineFragment.kt | 9 ++++----- .../app/features/home/room/detail/TimelineViewModel.kt | 7 +++++++ 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 64670c73ac..c1e3b58a80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -117,4 +117,6 @@ sealed class RoomDetailAction : VectorViewModelAction { // Live Location object StopLiveLocationSharing : RoomDetailAction() + + object OpenElementCallWidget : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index b47b46c152..7aa7d5a877 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -93,8 +93,7 @@ data class RoomDetailViewState( return asyncRoomSummary.invoke()?.isDirect ?: true || // When there is only one member, a warning will be displayed when the user // clicks on the menu item to start a call - asyncRoomSummary.invoke()?.joinedMembersCount == 1 || - hasActiveElementCallWidget() + asyncRoomSummary.invoke()?.joinedMembersCount == 1 } fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt index ba691de5d2..8d2d086275 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt @@ -47,6 +47,11 @@ class StartCallActionsHandler( } private fun handleCallRequest(isVideoCall: Boolean) = withState(timelineViewModel) { state -> + if (state.hasActiveElementCallWidget() && !isVideoCall) { + timelineViewModel.handle(RoomDetailAction.OpenElementCallWidget) + return@withState + } + val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState when (roomSummary.joinedMembersCount) { 1 -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 27ba12e4d7..a3acecdd38 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1088,12 +1088,11 @@ class TimelineFragment @Inject constructor( val hasCallInRoom = callManager.getCallsByRoomId(state.roomId).isNotEmpty() || state.jitsiState.hasJoined val callButtonsEnabled = !hasCallInRoom && when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { 1 -> false - 2 -> state.isAllowedToStartWebRTCCall || state.hasActiveElementCallWidget() - else -> state.isAllowedToManageWidgets || state.hasActiveElementCallWidget() - } - setOf(R.id.voice_call, R.id.video_call).forEach { - menu.findItem(it).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40 + 2 -> state.isAllowedToStartWebRTCCall + else -> state.isAllowedToManageWidgets } + menu.findItem(R.id.video_call).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40 + menu.findItem(R.id.voice_call).icon?.alpha = if (callButtonsEnabled || state.hasActiveElementCallWidget()) 0xFF else 0x40 val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps) val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 71b6b22aed..c73ed3b672 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -467,6 +467,13 @@ class TimelineViewModel @AssistedInject constructor( } is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId) RoomDetailAction.StopLiveLocationSharing -> handleStopLiveLocationSharing() + RoomDetailAction.OpenElementCallWidget -> handleOpenElementCallWidget() + } + } + + private fun handleOpenElementCallWidget() = withState { state -> + if (state.hasActiveElementCallWidget()) { + _viewEvents.post(RoomDetailViewEvents.OpenElementCallWidget) } } From 81f3e4a360a8b89fb4727f8630eb50797eff1baa Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 16:29:34 +0300 Subject: [PATCH 065/133] Lint fixes. --- .../java/im/vector/app/features/widgets/WidgetActivity.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 90f1ab4bbb..a068d57936 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -24,7 +24,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.res.Configuration import android.graphics.drawable.Icon import android.os.Build import android.util.Rational @@ -34,7 +33,6 @@ import androidx.core.util.Consumer import androidx.core.view.isVisible import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel -import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.addFragment @@ -184,7 +182,7 @@ class WidgetActivity : VectorBaseActivity() { private val pictureInPictureModeChangedInfoConsumer = Consumer { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return@Consumer - + if (isInPictureInPictureMode) { hangupBroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { From b38911751e3c7ec30459bff7cb2c19e439317d54 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Jul 2022 17:28:01 +0300 Subject: [PATCH 066/133] Changelog added. --- changelog.d/6616.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6616.feature diff --git a/changelog.d/6616.feature b/changelog.d/6616.feature new file mode 100644 index 0000000000..d013771764 --- /dev/null +++ b/changelog.d/6616.feature @@ -0,0 +1 @@ +Support element call widget From 0c289eba2b60b67432985f743a72407818ffa90b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Jul 2022 11:26:32 +0200 Subject: [PATCH 067/133] Fix crash observed on nightly build. MainActivity is not able to display alert because the theme is not compliant. Anyway we should not display alert on this screen. --- .../main/java/im/vector/app/features/popup/PopupAlertManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index 3ebcb3f318..40ef6d819e 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -26,6 +26,7 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.time.Clock import im.vector.app.core.utils.isAnimationEnabled +import im.vector.app.features.MainActivity import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.signout.hard.SignedOutActivity @@ -302,6 +303,7 @@ class PopupAlertManager @Inject constructor( private fun shouldBeDisplayedIn(alert: VectorAlert?, activity: Activity): Boolean { return alert != null && + activity !is MainActivity && activity !is PinActivity && activity !is SignedOutActivity && activity !is AnalyticsOptInActivity && From 8019eb55fac19f267e2070d47be3ab205e58fc8f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Jul 2022 11:43:58 +0200 Subject: [PATCH 068/133] Add missing `)` --- .github/workflows/triage-labelled.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 5cecc2e2aa..90f03779a6 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -250,7 +250,7 @@ jobs: github.repository == 'vector-im/element-android' && (contains(github.event.issue.labels.*.name, 'Z-BBQ-Alpha') || contains(github.event.issue.labels.*.name, 'Z-BBQ-Beta') || - contains(github.event.issue.labels.*.name, 'Z-BBQ-Release' || + contains(github.event.issue.labels.*.name, 'Z-BBQ-Release') || contains(github.event.issue.labels.*.name, 'Z-Banquet-Alpha') || contains(github.event.issue.labels.*.name, 'Z-Banquet-Beta') || contains(github.event.issue.labels.*.name, 'Z-Banquet-Release')) From 70c9c8a21de5dcaf9f16e16089811840cb178833 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 22 Jul 2022 11:43:38 +0200 Subject: [PATCH 069/133] Navigate to location live map when tapping the live status bar in timeline --- .../vector/app/features/home/room/detail/TimelineFragment.kt | 3 +++ .../app/features/location/live/LocationLiveStatusView.kt | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 562f2d4aea..c32f2fe916 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -859,6 +859,9 @@ class TimelineFragment @Inject constructor( views.locationLiveStatusIndicator.stopButton.debouncedClicks { timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing) } + views.locationLiveStatusIndicator.root.debouncedClicks { + navigateToLocationLiveMap() + } } private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt index a4c58c9e5b..ac7519918c 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt @@ -19,6 +19,7 @@ package im.vector.app.features.location.live import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import android.view.View import android.widget.Button import androidx.constraintlayout.widget.ConstraintLayout import im.vector.app.databinding.ViewLocationLiveStatusBinding @@ -34,6 +35,9 @@ class LocationLiveStatusView @JvmOverloads constructor( this ) + val root: View + get() = binding.root + val stopButton: Button get() = binding.locationLiveStatusStop } From 03ebad14c444a804eb4b4ebbe3c7e3d716838b60 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 22 Jul 2022 11:50:23 +0200 Subject: [PATCH 070/133] Adding changelog entry --- changelog.d/6625.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6625.misc diff --git a/changelog.d/6625.misc b/changelog.d/6625.misc new file mode 100644 index 0000000000..68a58c38fa --- /dev/null +++ b/changelog.d/6625.misc @@ -0,0 +1 @@ +[Location sharing] - OnTap on the top live status bar, display the expanded map view From d631c709d686249661fe664d160e0f4657101f7c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 22 Jul 2022 14:33:45 +0300 Subject: [PATCH 071/133] Create use case to check webview permissions. --- .../api/session/widgets/model/WidgetType.kt | 2 +- .../utils/CheckWebViewPermissionsUseCase.kt | 52 +++++++++++++++++++ .../vector/app/core/utils/PermissionsTools.kt | 27 ---------- .../app/features/widgets/WidgetFragment.kt | 6 ++- .../features/widgets/webview/WidgetWebView.kt | 9 ++-- 5 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCase.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt index 24f3a155ed..f02fe4f9de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt @@ -29,7 +29,7 @@ private val DEFINED_TYPES by lazy { WidgetType.Grafana, WidgetType.Custom, WidgetType.IntegrationManager, - WidgetType.ElementCall + WidgetType.ElementCall, ) } diff --git a/vector/src/main/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCase.kt b/vector/src/main/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCase.kt new file mode 100644 index 0000000000..df84e24f90 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCase.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 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.utils + +import android.app.Activity +import android.content.pm.PackageManager +import android.webkit.PermissionRequest +import androidx.core.content.ContextCompat +import javax.inject.Inject + +class CheckWebViewPermissionsUseCase @Inject constructor() { + + /** + * Checks if required WebView permissions are already granted system level. + * @param activity the calling Activity that is requesting the permissions (or fragment parent) + * @param request WebView permission request of onPermissionRequest function + * @return true if WebView permissions are already granted, false otherwise + */ + fun execute(activity: Activity, request: PermissionRequest): Boolean { + return request.resources.all { + when (it) { + PermissionRequest.RESOURCE_AUDIO_CAPTURE -> { + PERMISSIONS_FOR_AUDIO_IP_CALL.all { permission -> + ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED + } + } + PermissionRequest.RESOURCE_VIDEO_CAPTURE -> { + PERMISSIONS_FOR_VIDEO_IP_CALL.all { permission -> + ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED + } + } + else -> { + false + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 051a6cd8ce..9ad95d3c55 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -19,7 +19,6 @@ package im.vector.app.core.utils import android.Manifest import android.app.Activity import android.content.pm.PackageManager -import android.webkit.PermissionRequest import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -138,32 +137,6 @@ fun checkPermissions( } } -/** - * Checks if required WebView permissions are already granted system level. - * @param activity the calling Activity that is requesting the permissions (or fragment parent) - * @param request WebView permission request of onPermissionRequest function - * @return true if WebView permissions are already granted, false otherwise - */ -fun checkWebViewPermissions(activity: Activity, request: PermissionRequest): Boolean { - return request.resources.all { - when (it) { - PermissionRequest.RESOURCE_AUDIO_CAPTURE -> { - PERMISSIONS_FOR_AUDIO_IP_CALL.all { permission -> - ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED - } - } - PermissionRequest.RESOURCE_VIDEO_CAPTURE -> { - PERMISSIONS_FOR_VIDEO_IP_CALL.all { permission -> - ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED - } - } - else -> { - false - } - } - } -} - /** * To be call after the permission request. * diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index 390bd3c88b..9ac085fa89 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -43,6 +43,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider +import im.vector.app.core.utils.CheckWebViewPermissionsUseCase import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentRoomWidgetBinding import im.vector.app.features.webview.WebEventListener @@ -65,7 +66,8 @@ data class WidgetArgs( ) : Parcelable class WidgetFragment @Inject constructor( - private val permissionUtils: WebviewPermissionUtils + private val permissionUtils: WebviewPermissionUtils, + private val checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase, ) : VectorBaseFragment(), WebEventListener, @@ -81,7 +83,7 @@ class WidgetFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.widgetWebView.setupForWidget(requireActivity(), this) + views.widgetWebView.setupForWidget(requireActivity(), checkWebViewPermissionsUseCase, this) if (fragmentArgs.kind.isAdmin()) { viewModel.getPostAPIMediator().setWebView(views.widgetWebView) } diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index 2f3b449b1a..ac9930866f 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -24,13 +24,16 @@ import android.webkit.PermissionRequest import android.webkit.WebChromeClient import android.webkit.WebView import im.vector.app.R -import im.vector.app.core.utils.checkWebViewPermissions +import im.vector.app.core.utils.CheckWebViewPermissionsUseCase import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.webview.VectorWebViewClient import im.vector.app.features.webview.WebEventListener @SuppressLint("NewApi") -fun WebView.setupForWidget(activity: Activity, eventListener: WebEventListener) { +fun WebView.setupForWidget(activity: Activity, + checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase, + eventListener: WebEventListener, +) { // xml value seems ignored setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorSurface)) @@ -63,7 +66,7 @@ fun WebView.setupForWidget(activity: Activity, eventListener: WebEventListener) // Permission requests webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { - if (checkWebViewPermissions(activity, request)) { + if (checkWebViewPermissionsUseCase.execute(activity, request)) { request.grant(request.resources) } else { eventListener.onPermissionRequest(request) From 242c14a1569a33947309d1562aca6ff1b8515a1c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 22 Jul 2022 14:34:06 +0300 Subject: [PATCH 072/133] Write test for the use case. --- .../CheckWebViewPermissionsUseCaseTest.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt diff --git a/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt new file mode 100644 index 0000000000..8d328d75c5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022 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.utils + +import android.app.Activity +import android.content.pm.PackageManager +import android.net.Uri +import android.webkit.PermissionRequest +import androidx.core.content.ContextCompat.checkSelfPermission +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.amshove.kluent.shouldBe +import org.junit.After +import org.junit.Before +import org.junit.Test + +class CheckWebViewPermissionsUseCaseTest { + + private val checkWebViewPermissionsUseCase = CheckWebViewPermissionsUseCase() + + private val activity = mockk().apply { + every { applicationContext } returns mockk() + } + + @Before + fun setup() { + MockKAnnotations.init(this) + mockkStatic("androidx.core.content.ContextCompat") + } + + @After + fun tearDown() { + unmockkStatic("androidx.core.content.ContextCompat") + } + + @Test + fun `given an audio permission is granted when the web client requests audio permission then use case returns true`() { + val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) + + every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED + + checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + } + + @Test + fun `given a camera permission is granted when the web client requests video permission then use case returns true`() { + val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + + every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED + + checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + } + + @Test + fun `given an audio and camera permissions are granted when the web client requests audio and video permissions then use case returns true`() { + val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + + every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED + + checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + } + + @Test + fun `given an audio permission is granted but camera isn't when the web client requests audio and video permissions then use case returns false`() { + val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + + PERMISSIONS_FOR_AUDIO_IP_CALL.forEach { + every { checkSelfPermission(activity.applicationContext, it) } returns PackageManager.PERMISSION_GRANTED + } + PERMISSIONS_FOR_VIDEO_IP_CALL.forEach { + every { checkSelfPermission(activity.applicationContext, it) } returns PackageManager.PERMISSION_DENIED + } + + checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe false + } + + @Test + fun `given an audio and camera permissions are granted when the web client requests another permission then use case returns false`() { + val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_MIDI_SYSEX)) + + every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED + + checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe false + } + + private fun givenAPermissionRequest(resources: Array): PermissionRequest { + return object : PermissionRequest() { + override fun getOrigin(): Uri { + return mockk() + } + + override fun getResources(): Array { + return resources + } + + override fun grant(resources: Array?) { + } + + override fun deny() { + } + } + } +} From 23a25cf24023c99a6e64a0049f3a13cc3629cdab Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 22 Jul 2022 14:36:02 +0300 Subject: [PATCH 073/133] Rename widget action. --- .../main/java/im/vector/app/features/widgets/WidgetAction.kt | 2 +- .../java/im/vector/app/features/widgets/WidgetActivity.kt | 2 +- .../java/im/vector/app/features/widgets/WidgetViewModel.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt index f525af6109..d5d8e95aa6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetAction.kt @@ -26,5 +26,5 @@ sealed class WidgetAction : VectorViewModelAction { object DeleteWidget : WidgetAction() object RevokeWidget : WidgetAction() object OnTermsReviewed : WidgetAction() - object HangupElementCall : WidgetAction() + object CloseWidget : WidgetAction() } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index a068d57936..92b070c8d0 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -189,7 +189,7 @@ class WidgetActivity : VectorBaseActivity() { if (intent?.action == ACTION_MEDIA_CONTROL) { val controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0) if (controlType == CONTROL_TYPE_HANGUP) { - viewModel.handle(WidgetAction.HangupElementCall) + viewModel.handle(WidgetAction.CloseWidget) } } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 17dc244f4c..ecd6ca2fd6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -147,11 +147,11 @@ class WidgetViewModel @AssistedInject constructor( WidgetAction.DeleteWidget -> handleDeleteWidget() WidgetAction.RevokeWidget -> handleRevokeWidget() WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) - WidgetAction.HangupElementCall -> handleHangupElementCall() + WidgetAction.CloseWidget -> handleCloseWidget() } } - private fun handleHangupElementCall() { + private fun handleCloseWidget() { _viewEvents.post(WidgetViewEvents.Close()) } From 792fca8400088fded16d5924ef9326c8aadf3c48 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 22 Jul 2022 14:48:58 +0300 Subject: [PATCH 074/133] Code review fix. --- .../java/im/vector/app/features/widgets/WidgetActivity.kt | 3 ++- .../java/im/vector/app/features/widgets/WidgetViewState.kt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 92b070c8d0..0b78d8d2f1 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -41,6 +41,7 @@ import im.vector.app.databinding.ActivityWidgetBinding import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewEvents import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewModel +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Content import java.io.Serializable @@ -145,7 +146,7 @@ class WidgetActivity : VectorBaseActivity() { override fun onUserLeaveHint() { super.onUserLeaveHint() val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(Mavericks.KEY_ARG) - if (widgetArgs?.kind == WidgetKind.ELEMENT_CALL) { + if (widgetArgs?.kind?.supportsPictureInPictureMode().orFalse()) { enterPictureInPicture() } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt index 7619fea766..cd2ed23980 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt @@ -39,6 +39,10 @@ enum class WidgetKind(@StringRes val nameRes: Int, val screenId: String?) { fun isAdmin(): Boolean { return this == STICKER_PICKER || this == INTEGRATION_MANAGER } + + fun supportsPictureInPictureMode(): Boolean { + return this == ELEMENT_CALL + } } data class WidgetViewState( From 99a906fe9b584e5adaeb4fdddd62a158bc1f2efc Mon Sep 17 00:00:00 2001 From: ClaireG Date: Fri, 22 Jul 2022 15:35:01 +0200 Subject: [PATCH 075/133] replyTo are not updated if the original message is edited (#6404) --- changelog.d/5546.bugfix | 1 + .../room/send/LocalEchoEventFactory.kt | 30 +++++--- .../session/room/timeline/DefaultTimeline.kt | 3 + .../room/timeline/DefaultTimelineService.kt | 3 + .../room/timeline/LoadTimelineStrategy.kt | 4 ++ .../session/room/timeline/TimelineChunk.kt | 69 ++++++++++++++++++- 6 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 changelog.d/5546.bugfix diff --git a/changelog.d/5546.bugfix b/changelog.d/5546.bugfix new file mode 100644 index 0000000000..2dea6591ce --- /dev/null +++ b/changelog.d/5546.bugfix @@ -0,0 +1 @@ +ReplyTo are not updated if the original message is edited diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index f9fa64ddfe..6c5df1f4e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -574,17 +574,13 @@ internal class LocalEchoEventFactory @Inject constructor( return clock.epochMillis() } - /** - * Creates a reply to a regular timeline Event or a thread Event if needed. - */ - fun createReplyTextEvent( - roomId: String, + fun createReplyTextContent( eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean, rootThreadEventId: String? = null, showInThread: Boolean - ): Event? { + ): MessageContent? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -610,7 +606,7 @@ internal class LocalEchoEventFactory @Inject constructor( val replyFallback = buildReplyFallback(body, userId, replyText.toString()) val eventId = eventReplied.root.eventId ?: return null - val content = MessageTextContent( + return MessageTextContent( msgType = MessageType.MSGTYPE_TEXT, format = MessageFormat.FORMAT_MATRIX_HTML, body = replyFallback, @@ -621,7 +617,23 @@ internal class LocalEchoEventFactory @Inject constructor( showInThread = showInThread ) ) - return createMessageEvent(roomId, content) + } + + /** + * Creates a reply to a regular timeline Event or a thread Event if needed. + */ + fun createReplyTextEvent( + roomId: String, + eventReplied: TimelineEvent, + replyText: CharSequence, + autoMarkdown: Boolean, + rootThreadEventId: String? = null, + showInThread: Boolean, + ): Event? { + val content = createReplyTextContent(eventReplied, replyText, autoMarkdown, rootThreadEventId, showInThread) + return content?.let { + createMessageEvent(roomId, it) + } } /** @@ -682,7 +694,7 @@ internal class LocalEchoEventFactory @Inject constructor( * In case of an edit of a reply the last content is not * himself a reply, but it will contain the fallbacks, so we have to trim them. */ - private fun bodyForReply(content: MessageContent?, isReply: Boolean): TextContent { + fun bodyForReply(content: MessageContent?, isReply: Boolean): TextContent { when (content?.msgType) { MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT, 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 4eaac67e5a..66bb8ae073 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,6 +42,7 @@ import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler @@ -63,6 +64,7 @@ internal class DefaultTimeline( private val settings: TimelineSettings, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val clock: Clock, + localEchoEventFactory: LocalEchoEventFactory, stateEventDataSource: StateEventDataSource, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, @@ -114,6 +116,7 @@ internal class DefaultTimeline( onNewTimelineEvents = this::onNewTimelineEvents, stateEventDataSource = stateEventDataSource, matrixCoroutineDispatchers = coroutineDispatchers, + localEchoEventFactory = localEchoEventFactory ) private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live) 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 53c0253876..e99a916d06 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 @@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper 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.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler @@ -55,6 +56,7 @@ internal class DefaultTimelineService @AssistedInject constructor( private val timelineEventDataSource: TimelineEventDataSource, private val clock: Clock, private val stateEventDataSource: StateEventDataSource, + private val localEchoEventFactory: LocalEchoEventFactory ) : TimelineService { @AssistedFactory @@ -82,6 +84,7 @@ internal class DefaultTimelineService @AssistedInject constructor( lightweightSettingsStorage = lightweightSettingsStorage, clock = clock, stateEventDataSource = stateEventDataSource, + localEchoEventFactory = localEchoEventFactory ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index d81a115676..3d82f77b75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.util.time.Clock @@ -106,6 +107,7 @@ internal class LoadTimelineStrategy constructor( val onNewTimelineEvents: (List) -> Unit, val stateEventDataSource: StateEventDataSource, val matrixCoroutineDispatchers: MatrixCoroutineDispatchers, + val localEchoEventFactory: LocalEchoEventFactory ) private var getContextLatch: CompletableDeferred? = null @@ -341,6 +343,8 @@ internal class LoadTimelineStrategy constructor( initialEventId = mode.originEventId(), onBuiltEvents = dependencies.onEventsUpdated, onEventsDeleted = dependencies.onEventsDeleted, + realm = dependencies.realm, + localEchoEventFactory = dependencies.localEchoEventFactory ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 7fa36969b1..3068dec0dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener +import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery @@ -26,10 +27,18 @@ import io.realm.Sort import kotlinx.coroutines.CompletableDeferred import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull +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.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.api.session.room.timeline.isReply import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper @@ -37,12 +46,15 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import timber.log.Timber import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference /** * This is a wrapper around a ChunkEntity in the database. @@ -66,6 +78,8 @@ internal class TimelineChunk( private val initialEventId: String?, private val onBuiltEvents: (Boolean) -> Unit, private val onEventsDeleted: () -> Unit, + private val realm: AtomicReference, + val localEchoEventFactory: LocalEchoEventFactory, ) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) @@ -411,9 +425,56 @@ internal class TimelineChunk( private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( timelineEventEntity = eventEntity, buildReadReceipts = timelineSettings.buildReadReceipts - ).let { + ).let { timelineEvent -> // eventually enhance with ui echo? - (uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it) + uiEchoManager?.decorateEventWithReactionUiEcho(timelineEvent) + + if (timelineEvent.isReply()) { + createNewEncryptedRepliedEvent(timelineEvent)?.let { newEvent -> + timelineEvent.copy(root = newEvent) + } ?: timelineEvent + } else timelineEvent + } + + private fun createNewEncryptedRepliedEvent(currentTimelineEvent: TimelineEvent): Event? { + val relatesEventId = if (currentTimelineEvent.isEncrypted()) { + currentTimelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + } else { + currentTimelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + } + return relatesEventId?.let { eventId -> + val timeLineEventEntity = TimelineEventEntity.where( + realm.get(), + roomId, + eventId + ).findFirst() + + val replyText = localEchoEventFactory + .bodyForReply(currentTimelineEvent.getLastMessageContent(), true).formattedText ?: "" + + timeLineEventEntity?.let { timelineEventEntity -> + val newContent = localEchoEventFactory.createReplyTextContent( + timelineEventMapper.map(timelineEventEntity), + replyText, + false, + showInThread = false + ).toContent() + val event = currentTimelineEvent.root + Event( + roomId = event.roomId, + originServerTs = event.originServerTs, + senderId = event.senderId, + eventId = currentTimelineEvent.eventId, + type = EventType.MESSAGE, + content = newContent, + unsignedData = UnsignedData(age = null, transactionId = currentTimelineEvent.eventId), + ).apply { + this.sendState = event.sendState + this.ageLocalTs = event.ageLocalTs + this.threadDetails = event.threadDetails + } + } + } } /** @@ -580,7 +641,9 @@ internal class TimelineChunk( lightweightSettingsStorage = lightweightSettingsStorage, initialEventId = null, onBuiltEvents = this.onBuiltEvents, - onEventsDeleted = this.onEventsDeleted + onEventsDeleted = this.onEventsDeleted, + realm = realm, + localEchoEventFactory = localEchoEventFactory ) } From 5c253bbd280892371f28102d44dede815751ce95 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 22 Jul 2022 17:21:23 +0300 Subject: [PATCH 076/133] Code review fixes. --- .../CheckWebViewPermissionsUseCaseTest.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt index 8d328d75c5..fe082ab5b6 100644 --- a/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/core/utils/CheckWebViewPermissionsUseCaseTest.kt @@ -17,15 +17,16 @@ package im.vector.app.core.utils import android.app.Activity +import android.content.Context import android.content.pm.PackageManager import android.net.Uri import android.webkit.PermissionRequest import androidx.core.content.ContextCompat.checkSelfPermission -import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic +import io.mockk.verify import org.amshove.kluent.shouldBe import org.junit.After import org.junit.Before @@ -41,7 +42,6 @@ class CheckWebViewPermissionsUseCaseTest { @Before fun setup() { - MockKAnnotations.init(this) mockkStatic("androidx.core.content.ContextCompat") } @@ -53,34 +53,33 @@ class CheckWebViewPermissionsUseCaseTest { @Test fun `given an audio permission is granted when the web client requests audio permission then use case returns true`() { val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) - every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + verifyPermissionsChecked(activity.applicationContext, PERMISSIONS_FOR_AUDIO_IP_CALL) } @Test fun `given a camera permission is granted when the web client requests video permission then use case returns true`() { val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) - every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + verifyPermissionsChecked(activity.applicationContext, PERMISSIONS_FOR_VIDEO_IP_CALL) } @Test fun `given an audio and camera permissions are granted when the web client requests audio and video permissions then use case returns true`() { val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_VIDEO_CAPTURE)) - every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe true + verifyPermissionsChecked(activity.applicationContext, PERMISSIONS_FOR_AUDIO_IP_CALL + PERMISSIONS_FOR_VIDEO_IP_CALL) } @Test fun `given an audio permission is granted but camera isn't when the web client requests audio and video permissions then use case returns false`() { val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_VIDEO_CAPTURE)) - PERMISSIONS_FOR_AUDIO_IP_CALL.forEach { every { checkSelfPermission(activity.applicationContext, it) } returns PackageManager.PERMISSION_GRANTED } @@ -89,15 +88,22 @@ class CheckWebViewPermissionsUseCaseTest { } checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe false + verifyPermissionsChecked(activity.applicationContext, PERMISSIONS_FOR_AUDIO_IP_CALL + PERMISSIONS_FOR_VIDEO_IP_CALL.first()) } @Test fun `given an audio and camera permissions are granted when the web client requests another permission then use case returns false`() { val permissionRequest = givenAPermissionRequest(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE, PermissionRequest.RESOURCE_MIDI_SYSEX)) - every { checkSelfPermission(activity.applicationContext, any()) } returns PackageManager.PERMISSION_GRANTED checkWebViewPermissionsUseCase.execute(activity, permissionRequest) shouldBe false + verifyPermissionsChecked(activity.applicationContext, PERMISSIONS_FOR_AUDIO_IP_CALL) + } + + private fun verifyPermissionsChecked(context: Context, permissions: List) { + permissions.forEach { + verify { checkSelfPermission(context, it) } + } } private fun givenAPermissionRequest(resources: Array): PermissionRequest { From 83f322980502b707adab3917a7b75de639b22ca8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Jul 2022 16:52:05 +0200 Subject: [PATCH 077/133] Add in-app alert to let user know if a new version is available. --- dependencies.gradle | 5 +- vector/build.gradle | 6 +++ .../im/vector/app/nightly/NightlyProxy.kt | 25 ++++++++++ .../im/vector/app/nightly/NightlyProxy.kt | 47 +++++++++++++++++++ .../vector/app/features/home/HomeActivity.kt | 5 ++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt create mode 100644 vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt diff --git a/dependencies.gradle b/dependencies.gradle index 97b4ad2ea3..44e62f7ae4 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -15,6 +15,7 @@ def gradle = "7.1.3" def kotlin = "1.6.21" def kotlinCoroutines = "1.6.4" def dagger = "2.42" +def appDistribution = "16.0.0-beta03" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" @@ -83,7 +84,9 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.6.1" + 'material' : "com.google.android.material:material:1.6.1", + 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", + 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", diff --git a/vector/build.gradle b/vector/build.gradle index e4313770c4..0f65da4c2e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -449,6 +449,12 @@ dependencies { implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks + // Nightly + // API-only library + gplayImplementation libs.google.appdistributionApi + // Full SDK implementation + gplayImplementation libs.google.appdistribution + // Work implementation libs.androidx.work diff --git a/vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt b/vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt new file mode 100644 index 0000000000..3683a86c50 --- /dev/null +++ b/vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.nightly + +import javax.inject.Inject + +class NightlyProxy @Inject constructor() { + fun updateIfNewReleaseAvailable() { + // No op + } +} diff --git a/vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt b/vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt new file mode 100644 index 0000000000..73262e21a9 --- /dev/null +++ b/vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 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.nightly + +import com.google.firebase.appdistribution.FirebaseAppDistribution +import com.google.firebase.appdistribution.FirebaseAppDistributionException +import timber.log.Timber +import javax.inject.Inject + +class NightlyProxy @Inject constructor() { + fun updateIfNewReleaseAvailable() { + val firebaseAppDistribution = FirebaseAppDistribution.getInstance() + firebaseAppDistribution.updateIfNewReleaseAvailable() + .addOnProgressListener { up -> + Timber.d("FirebaseAppDistribution progress: ${up.updateStatus}. ${up.apkBytesDownloaded}/${up.apkFileTotalBytes}") + } + .addOnFailureListener { e -> + if (e is FirebaseAppDistributionException) { + when (e.errorCode) { + FirebaseAppDistributionException.Status.NOT_IMPLEMENTED -> { + // SDK did nothing. This is expected when building for Play. + } + else -> { + // Handle other errors. + Timber.e(e, "FirebaseAppDistribution error, status: ${e.errorCode}") + } + } + } else { + Timber.e(e, "FirebaseAppDistribution - other error") + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index ff114e4db9..f1848e653f 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -78,6 +78,7 @@ import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel +import im.vector.app.nightly.NightlyProxy import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -132,6 +133,7 @@ class HomeActivity : @Inject lateinit var appStateHandler: AppStateHandler @Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var fcmHelper: FcmHelper + @Inject lateinit var nightlyProxy: NightlyProxy private val createSpaceResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { @@ -533,6 +535,9 @@ class HomeActivity : // Force remote backup state update to update the banner if needed serverBackupStatusViewModel.refreshRemoteStateIfNeeded() + + // Check nightly + nightlyProxy.updateIfNewReleaseAvailable() } override fun getMenuRes() = R.menu.home From 985e09a68cfff6d7b0abecd9c96ffc679ba1cf15 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 11:03:15 +0200 Subject: [PATCH 078/133] Remove abandoned TODO --- .../timeline/factory/LiveLocationShareMessageItemFactory.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index a4c906d97b..493602a291 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -102,7 +102,6 @@ class LiveLocationShareMessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes, runningState: LiveLocationShareViewState.Running, ): MessageLiveLocationItem { - // TODO only render location if enabled in preferences: to be handled in a next PR val width = timelineMediaSizeProvider.getMaxSize().first val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) From d632c866a2a6a3b7c27b7d7df91e4a23406ddd92 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 11:28:28 +0200 Subject: [PATCH 079/133] Center crop the map --- .../timeline/item/DefaultLiveLocationShareStatusItem.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt index c421efda12..df114fe101 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt @@ -20,6 +20,8 @@ import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.widget.ImageView import androidx.core.view.updateLayoutParams +import com.bumptech.glide.load.MultiTransformation +import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners import im.vector.app.R @@ -51,7 +53,7 @@ class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem { } GlideApp.with(mapImageView) .load(R.drawable.bg_no_location_map) - .transform(mapCornerTransformation) + .transform(MultiTransformation(CenterCrop(), mapCornerTransformation)) .into(mapImageView) } From 2a0331dc04e7b009eb3a1bf9233a9e60ae24d6fc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 11:46:12 +0200 Subject: [PATCH 080/133] Fixing title position inside banner for loading and stop items --- .../item_timeline_event_live_location_inactive_stub.xml | 7 +++++-- .../item_timeline_event_live_location_start_stub.xml | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml index d5a0cefb28..ba0ff33230 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -56,13 +56,16 @@ From 4bb9a68603e9fa5fc9f16f90512030b72d6054f5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 12:00:39 +0200 Subject: [PATCH 081/133] Fixing title position inside banner for active live item --- .../res/layout/view_location_live_message_banner.xml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/view_location_live_message_banner.xml b/vector/src/main/res/layout/view_location_live_message_banner.xml index 5c8f3a8970..f13696a5c7 100644 --- a/vector/src/main/res/layout/view_location_live_message_banner.xml +++ b/vector/src/main/res/layout/view_location_live_message_banner.xml @@ -34,11 +34,15 @@ From 7f43a1bb06a3244657250f89ea1ac7aafa5bdbe0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 12:18:07 +0200 Subject: [PATCH 082/133] Updating reply description text --- .../sdk/internal/session/room/send/LocalEchoEventFactory.kt | 2 +- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +- .../app/features/location/LocationSharingAndroidService.kt | 2 +- vector/src/main/res/values/strings.xml | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 6c5df1f4e4..9f71d476c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -713,7 +713,7 @@ internal class LocalEchoEventFactory @Inject constructor( MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") - MessageType.MSGTYPE_BEACON_INFO -> return TextContent(content.body.ensureNotEmpty() ?: "shared live location.") + MessageType.MSGTYPE_BEACON_INFO -> return TextContent(content.body.ensureNotEmpty() ?: "Live location") MessageType.MSGTYPE_POLL_START -> { return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "") } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index f336ffc67c..eb27e7d996 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1257,7 +1257,7 @@ class TimelineFragment @Inject constructor( val nonFormattedBody = when (messageContent) { is MessageAudioContent -> getAudioContentBodyText(messageContent) is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() - is MessageBeaconInfoContent -> getString(R.string.sent_live_location) + is MessageBeaconInfoContent -> getString(R.string.live_location_description) else -> messageContent?.body.orEmpty() } var formattedBody: CharSequence? = null diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt index dd18658059..635c0bf87d 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt @@ -122,7 +122,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca ?.locationSharingService() ?.startLiveLocationShare( timeoutMillis = roomArgs.durationMillis, - description = getString(R.string.sent_live_location) + description = getString(R.string.live_location_description) ) updateLiveResult diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b162814a9d..1da05097f7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3126,6 +3126,8 @@ You don’t have permission to share live location You need to have the right permissions in order to share live location in this room. Share location + + Live location Show Message bubbles From 4a817fd9f56912c996008d15c7280f7310780c2e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 14:49:52 +0200 Subject: [PATCH 083/133] Fixing dark/light loading of the correct map drawable --- .../detail/timeline/item/DefaultLiveLocationShareStatusItem.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt index df114fe101..9a729caa31 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.widget.ImageView +import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.resource.bitmap.CenterCrop @@ -52,7 +53,7 @@ class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem { height = mapHeight } GlideApp.with(mapImageView) - .load(R.drawable.bg_no_location_map) + .load(ContextCompat.getDrawable(mapImageView.context, R.drawable.bg_no_location_map)) .transform(MultiTransformation(CenterCrop(), mapCornerTransformation)) .into(mapImageView) } From d1c631b55c525397e614e680314f4f3beee88bfe Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 20 Jul 2022 14:50:03 +0200 Subject: [PATCH 084/133] Adding changelog entry --- changelog.d/6607.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6607.misc diff --git a/changelog.d/6607.misc b/changelog.d/6607.misc new file mode 100644 index 0000000000..c56c3fca92 --- /dev/null +++ b/changelog.d/6607.misc @@ -0,0 +1 @@ +[Location sharing] - Small improvements of UI for live From 46684702a157070e0bcd879af72bfd4aade72180 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 25 Jul 2022 09:46:41 +0200 Subject: [PATCH 085/133] Hiding bottom sheet when no more lives --- .../location/live/map/LocationLiveMapViewFragment.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt index e19580f13b..bbb838a41a 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt @@ -22,6 +22,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.graphics.drawable.toBitmap +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -57,7 +59,6 @@ import javax.inject.Inject /** * Screen showing a map with all the current users sharing their live location in a room. */ - @AndroidEntryPoint class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment() { @@ -173,7 +174,13 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment) { - bottomSheetController.setData(userLocations) + if (userLocations.isEmpty()) { + views.bottomSheet.isGone = true + // TODO show Live location ended view + } else { + views.bottomSheet.isVisible = true + bottomSheetController.setData(userLocations) + } } private fun updateMap(userLiveLocations: List) { From 1276b1218888bb1ef5a52ac423658f41889e84ea Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 25 Jul 2022 09:48:26 +0200 Subject: [PATCH 086/133] Renaming banner view for running live --- .../timeline/item/MessageLiveLocationItem.kt | 10 +-- ...ew.kt => LocationLiveRunningBannerView.kt} | 6 +- .../item_timeline_event_location_stub.xml | 2 +- .../view_location_live_running_banner.xml | 67 +++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) rename vector/src/main/java/im/vector/app/features/location/live/{LocationLiveMessageBannerView.kt => LocationLiveRunningBannerView.kt} (96%) create mode 100644 vector/src/main/res/layout/view_location_live_running_banner.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index 84080eaad9..03b9a04008 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -26,7 +26,7 @@ import im.vector.app.core.resources.toTimestamp import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout -import im.vector.app.features.location.live.LocationLiveMessageBannerView +import im.vector.app.features.location.live.LocationLiveRunningBannerView import im.vector.app.features.location.live.LocationLiveMessageBannerViewState import org.threeten.bp.LocalDateTime @@ -52,9 +52,9 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem(R.id.locationLiveMessageBanner) + val locationLiveRunningBanner by bind(R.id.locationLiveMessageBanner) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveRunningBannerView.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt rename to vector/src/main/java/im/vector/app/features/location/live/LocationLiveRunningBannerView.kt index 51c7caed3a..1786ccdb8b 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveRunningBannerView.kt @@ -31,19 +31,19 @@ import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners import im.vector.app.R import im.vector.app.core.glide.GlideApp import im.vector.app.core.utils.TextUtils -import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding +import im.vector.app.databinding.ViewLocationLiveRunningBannerBinding import im.vector.app.features.themes.ThemeUtils import org.threeten.bp.Duration private const val REMAINING_TIME_COUNTER_INTERVAL_IN_MS = 1000L -class LocationLiveMessageBannerView @JvmOverloads constructor( +class LocationLiveRunningBannerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { - private val binding = ViewLocationLiveMessageBannerBinding.inflate( + private val binding = ViewLocationLiveRunningBannerBinding.inflate( LayoutInflater.from(context), this ) diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index a696140669..54de86e57f 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -45,7 +45,7 @@ app:layout_constraintTop_toBottomOf="@id/staticMapPinImageView" tools:visibility="visible" /> - + + + + + + + + + + +