diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml
index 7ac55427a9..582998d492 100644
--- a/.github/ISSUE_TEMPLATE/release.yml
+++ b/.github/ISSUE_TEMPLATE/release.yml
@@ -1,6 +1,6 @@
name: Release checklist
description: Checklist for each release. This template is only for the core team.
-title: "[Release] Element Android v"
+title: "[Release] Element Android v"
labels: [🚀 Release]
assignees:
- bmarty
@@ -10,7 +10,7 @@ body:
id: checklist
attributes:
label: Release checklist
- description: For the template example, we are releasing the version 1.1.10. Replace 1.1.10 with the version in the issue body.
+ description: For the template example, we are releasing the version 1.2.3. Replace 1.2.3 with the version in the issue body.
placeholder: |
If you are reading this, you have deleted the content of the release template: undo the deletion or start again.
value: |
@@ -22,35 +22,41 @@ body:
### Do the release
- - [ ] Create release with gitflow, branch name `release/1.1.10`
+ - [ ] Create release with gitflow, branch name `release/1.2.3`
- [ ] Check the crashes from the PlayStore
- - [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.1.10-dev
+ - [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.2.3-dev
- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
- - [ ] Create an account on matrix.org
- - [ ] Run towncrier: `towncrier --version v1.1.10 --draft` (remove `--draft` do write the file CHANGES.md)
+ - [ ] Create an account on matrix.org and do some smoke tests that the sanity test does not cover like: 1-1 call, 1-1 video call, Jitsi call for instance
+ - [ ] Run towncrier: `towncrier --version v1.2.3 --draft` (remove `--draft` do write the file CHANGES.md)
+ - [ ] Check that the folder `changelog.d` is empty. It can happen that some remaining files stay here
+ - [ ] Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things
- [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs
- - [ ] Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
- - [ ] Finish release with gitflow, delete the draft PR
- - [ ] Push `main` and the new tag `v1.1.10` to origin
+ - [ ] (optional) Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
+ - [ ] Finish release with gitflow, delete the draft PR (if created)
+ - [ ] Push `main` and the new tag `v1.2.3` to origin
- [ ] Checkout `develop`
- - [ ] Increase version in `./vector/build.gradle`
+ - [ ] Increase version (versionPatch + 2) in `./vector/build.gradle`
- [ ] Change the value of SDK_VERSION in the file `./matrix-sdk-android/build.gradle`
- [ ] Commit and push `develop`
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
- [ ] Install the APK on your phone to check that the upgrade went well (no init sync, etc.)
+ - [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md
+ - [ ] Add the 4 signed APKs to the GitHub release
+ - [ ] Ping the Android Internal room
+
+ ### Once tested and validated internally
+
- [ ] Create a new beta release on the GooglePlay console and upload the 4 signed Apks.
- [ ] Check that the version codes are correct
- [ ] Copy the fastlane change to the GooglePlay console in the section en-GB.
- [ ] Push to beta release to 100% of the users
- - [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md
- - [ ] Add the 4 signed APKs to the GitHub release
- - [ ] Ping the Android Internal room
- - [ ] Add an entry in the internal diary
+ - [ ] Notify the F-Droid team so that they can schedule the publication on F-Droid
### Once Live on PlayStore
- [ ] Ping the Android public room and update its topic
+ - [ ] Add an entry in the internal diary
### After at least 2 days
@@ -62,6 +68,8 @@ body:
### Android SDK2
+ The SDK2 and the sample app are released only when Element has been pushed to production.
+
- [ ] Checkout the `main` branch on Element Android project
#### On the SDK2 project
diff --git a/CHANGES.md b/CHANGES.md
index b1b0deee2c..ec022bc770 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,11 @@
+Changes in Element v1.3.18 (2022-02-03)
+=======================================
+
+Bugfixes 🐛
+----------
+ - Avoid deleting root event of CurrentState on gappy sync. In order to restore lost Events an initial sync may be triggered. ([#5137](https://github.com/vector-im/element-android/issues/5137))
+
+
Changes in Element v1.3.17 (2022-01-31)
=======================================
diff --git a/changelog.d/3907.bugfix b/changelog.d/3907.bugfix
new file mode 100644
index 0000000000..8cf6081cfc
--- /dev/null
+++ b/changelog.d/3907.bugfix
@@ -0,0 +1 @@
+Fixes non sans-serif font weights being ignored
\ No newline at end of file
diff --git a/changelog.d/4295.misc b/changelog.d/4295.misc
new file mode 100644
index 0000000000..652c5adc32
--- /dev/null
+++ b/changelog.d/4295.misc
@@ -0,0 +1 @@
+"Invite users to space" dialog now closed when user choose invite method
\ No newline at end of file
diff --git a/changelog.d/4304.misc b/changelog.d/4304.misc
new file mode 100644
index 0000000000..7fb6f2ecb0
--- /dev/null
+++ b/changelog.d/4304.misc
@@ -0,0 +1 @@
+Changed layout for space card and room card used at "explore room" screen and space/room invite dialogs
\ No newline at end of file
diff --git a/changelog.d/4315.misc b/changelog.d/4315.misc
new file mode 100644
index 0000000000..ff1271a604
--- /dev/null
+++ b/changelog.d/4315.misc
@@ -0,0 +1 @@
+Removed spaces restricted search hint dialogs
\ No newline at end of file
diff --git a/changelog.d/4641.misc b/changelog.d/4641.misc
new file mode 100644
index 0000000000..f02bf14fc1
--- /dev/null
+++ b/changelog.d/4641.misc
@@ -0,0 +1 @@
+Remove Search from room options if not available
diff --git a/changelog.d/4873.misc b/changelog.d/4873.misc
new file mode 100644
index 0000000000..328a62502f
--- /dev/null
+++ b/changelog.d/4873.misc
@@ -0,0 +1 @@
+Qr code scanning fragments merged into one
\ No newline at end of file
diff --git a/changelog.d/5038.bugfix b/changelog.d/5038.bugfix
new file mode 100644
index 0000000000..8092c21d4f
--- /dev/null
+++ b/changelog.d/5038.bugfix
@@ -0,0 +1 @@
+Fixing missing/intermittent notifications on the google play variant when wifi is enabled
\ No newline at end of file
diff --git a/changelog.d/5088.bugfix b/changelog.d/5088.bugfix
new file mode 100644
index 0000000000..bc702e5e94
--- /dev/null
+++ b/changelog.d/5088.bugfix
@@ -0,0 +1 @@
+Fixes call statuses in the timeline for missed/rejected calls and connected calls.
\ No newline at end of file
diff --git a/changelog.d/5128.bugfix b/changelog.d/5128.bugfix
new file mode 100644
index 0000000000..d26d0047a5
--- /dev/null
+++ b/changelog.d/5128.bugfix
@@ -0,0 +1 @@
+Fix fallback permalink when threads are disabled
\ No newline at end of file
diff --git a/changelog.d/5146.feature b/changelog.d/5146.feature
new file mode 100644
index 0000000000..a1832864c8
--- /dev/null
+++ b/changelog.d/5146.feature
@@ -0,0 +1 @@
+Support generic location pin
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/40103180.txt b/fastlane/metadata/android/en-US/changelogs/40103180.txt
new file mode 100644
index 0000000000..66e51f422a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40103180.txt
@@ -0,0 +1,2 @@
+Main changes in this version: send your location to any room. Edit poll.
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.18
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index b828855721..54b8fd3200 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -105,9 +105,6 @@
never
-
- sans
-
@style/PreferenceThemeOverlay.v14.Material@style/PinCodeScreenStyle
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index 790a0bfc7c..ee3f41635e 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -105,9 +105,6 @@
never
-
- sans
-
@style/PreferenceThemeOverlay.v14.Material@style/PinCodeScreenStyle
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 7d4bd0bc67..5137d18c00 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -31,7 +31,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.3.18\""
+ buildConfigField "String", "SDK_VERSION", "\"1.3.19\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index c090487c58..d07bd2d73a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -64,4 +64,12 @@ data class MessageLocationContent(
) : MessageContent {
fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri
+
+ /**
+ * @return true if the location asset is a user location, not a generic one.
+ */
+ fun isSelfLocation(): Boolean {
+ // Should behave like m.self if locationAsset is null
+ return locationAsset?.type == null || locationAsset.type == LocationAssetType.SELF
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index 9f2850e26a..09114436f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -66,8 +66,8 @@ interface RelationService {
* @param targetEventId the id of the event being reacted
* @param reaction the reaction (preferably emoji)
*/
- fun undoReaction(targetEventId: String,
- reaction: String): Cancelable
+ suspend fun undoReaction(targetEventId: String,
+ reaction: String): Cancelable
/**
* Edit a poll.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index 5a68937868..b70e6c1f80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -35,7 +35,7 @@ internal class MXOutboundSessionInfo(
val sessionLifetime = System.currentTimeMillis() - creationTime
if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
- Timber.v("## needsRotation() : Rotating megolm session after " + useCount + ", " + sessionLifetime + "ms")
+ Timber.v("## needsRotation() : Rotating megolm session after $useCount, ${sessionLifetime}ms")
needsRotation = true
}
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 e7ccae38d5..6c66ec9833 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
@@ -57,7 +57,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
) : RealmMigration {
companion object {
- const val SESSION_STORE_SCHEMA_VERSION = 22L
+ const val SESSION_STORE_SCHEMA_VERSION = 23L
}
/**
@@ -92,6 +92,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion <= 19) migrateTo20(realm)
if (oldVersion <= 20) migrateTo21(realm)
if (oldVersion <= 21) migrateTo22(realm)
+ if (oldVersion <= 22) migrateTo23(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -450,6 +451,22 @@ internal class RealmSessionStoreMigration @Inject constructor(
private fun migrateTo22(realm: DynamicRealm) {
Timber.d("Step 21 -> 22")
+ val listJoinedRoomIds = realm.where("RoomEntity")
+ .equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll()
+ .map { it.getString(RoomEntityFields.ROOM_ID) }
+
+ val hasMissingStateEvent = realm.where("CurrentStateEventEntity")
+ .`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray())
+ .isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null
+
+ if (hasMissingStateEvent) {
+ Timber.v("Has some missing state event, clear session cache")
+ realm.deleteAll()
+ }
+ }
+
+ private fun migrateTo23(realm: DynamicRealm) {
+ Timber.d("Step 22 -> 23")
val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
realm.schema.get("EventEntity")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
index ecb602019a..c45c27ed08 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
@@ -52,6 +52,9 @@ internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRo
if (deleteStateEvents) {
stateEvents.deleteAllFromRealm()
}
- timelineEvents.clearWith { it.deleteOnCascade(canDeleteRoot) }
+ timelineEvents.clearWith {
+ val deleteRoot = canDeleteRoot && (it.root?.stateKey == null || deleteStateEvents)
+ it.deleteOnCascade(deleteRoot)
+ }
deleteFromRealm()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
index c9c96b9cc1..8cc99c3d2f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
@@ -34,27 +34,29 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration,
if (LocalEcho.isLocalEchoId(eventId)) {
return true
}
- // If we don't know if the event has been read, we assume it's not
- var isEventRead = false
- Realm.getInstance(realmConfiguration).use { realm ->
- val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, true)
- // If latest event is from you we are sure the event is read
- if (latestEvent?.root?.sender == userId) {
- return true
- }
+ return Realm.getInstance(realmConfiguration).use { realm ->
val eventToCheck = TimelineEventEntity.where(realm, roomId, eventId).findFirst()
- isEventRead = when {
- eventToCheck == null -> false
- eventToCheck.root?.sender == userId -> true
- else -> {
- val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() ?: return@use
- val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst() ?: return@use
- readReceiptEvent.isMoreRecentThan(eventToCheck)
- }
+ when {
+ // The event doesn't exist locally, let's assume it hasn't been read
+ eventToCheck == null -> false
+ eventToCheck.root?.sender == userId -> true
+ // If new event exists and the latest event is from ourselves we can infer the event is read
+ latestEventIsFromSelf(realm, roomId, userId) -> true
+ eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId) -> true
+ else -> false
}
}
- return isEventRead
+}
+
+private fun latestEventIsFromSelf(realm: Realm, roomId: String, userId: String) = TimelineEventEntity.latestEvent(realm, roomId, true)
+ ?.root?.sender == userId
+
+private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String): Boolean {
+ return ReadReceiptEntity.where(realm, roomId, userId).findFirst()?.let { readReceipt ->
+ val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst()
+ readReceiptEvent?.isMoreRecentThan(this)
+ } ?: false
}
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
index 82cd682eae..55db64f309 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
@@ -66,7 +66,7 @@ internal class ThumbnailExtractor @Inject constructor(
thumbnail.recycle()
outputStream.reset()
} ?: run {
- Timber.e("Cannot extract video thumbnail at %s", attachment.queryUri.toString())
+ Timber.e("Cannot extract video thumbnail at ${attachment.queryUri}")
}
} catch (e: Exception) {
Timber.e(e, "Cannot extract video thumbnail")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
index 95e5771757..3abf28fdd4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
@@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.message.PollType
@@ -32,19 +31,15 @@ import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
-import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.di.UserId
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.send.queue.EventSenderProcessor
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.fetchCopyMap
import timber.log.Timber
@@ -54,15 +49,12 @@ internal class DefaultRelationService @AssistedInject constructor(
private val eventSenderProcessor: EventSenderProcessor,
private val eventFactory: LocalEchoEventFactory,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
- private val cryptoService: DefaultCryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask,
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
private val timelineEventMapper: TimelineEventMapper,
- @UserId private val userId: String,
- @SessionDatabase private val monarchy: Monarchy,
- private val taskExecutor: TaskExecutor) :
- RelationService {
+ @SessionDatabase private val monarchy: Monarchy
+) : RelationService {
@AssistedFactory
interface Factory {
@@ -84,39 +76,31 @@ internal class DefaultRelationService @AssistedInject constructor(
.none { it.addedByMe && it.key == reaction }) {
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
.also { saveLocalEcho(it) }
- return eventSenderProcessor.postEvent(event, false /* reaction are not encrypted*/)
+ eventSenderProcessor.postEvent(event, false /* reaction are not encrypted*/)
} else {
Timber.w("Reaction already added")
NoOpCancellable
}
}
- override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
+ override suspend fun undoReaction(targetEventId: String, reaction: String): Cancelable {
val params = FindReactionEventForUndoTask.Params(
roomId,
targetEventId,
reaction
)
- // TODO We should avoid using MatrixCallback internally
- val callback = object : MatrixCallback {
- override fun onSuccess(data: FindReactionEventForUndoTask.Result) {
- if (data.redactEventId == null) {
- Timber.w("Cannot find reaction to undo (not yet synced?)")
- // TODO?
- }
- data.redactEventId?.let { toRedact ->
- val redactEvent = eventFactory.createRedactEvent(roomId, toRedact, null)
- .also { saveLocalEcho(it) }
- eventSenderProcessor.postRedaction(redactEvent, null)
- }
- }
+
+ val data = findReactionEventForUndoTask.executeRetry(params, Int.MAX_VALUE)
+
+ return if (data.redactEventId == null) {
+ Timber.w("Cannot find reaction to undo (not yet synced?)")
+ // TODO?
+ NoOpCancellable
+ } else {
+ val redactEvent = eventFactory.createRedactEvent(roomId, data.redactEventId, null)
+ .also { saveLocalEcho(it) }
+ eventSenderProcessor.postRedaction(redactEvent, null)
}
- return findReactionEventForUndoTask
- .configureWith(params) {
- this.retryCount = Int.MAX_VALUE
- this.callback = callback
- }
- .executeBy(taskExecutor)
}
override fun editPoll(targetEvent: TimelineEvent,
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 385551ea94..8507b63d1f 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
@@ -36,7 +36,6 @@ 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.sync.handler.room.ThreadsAwarenessHandler
import timber.log.Timber
import java.util.Collections
@@ -198,11 +197,12 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
/**
* Simple log that displays the number and timeline of loaded events
*/
- private fun logLoadedFromStorage(loadedFromStorage: LoadedFromStorage, direction: Timeline.Direction) =
+ private fun logLoadedFromStorage(loadedFromStorage: LoadedFromStorage, direction: Timeline.Direction) {
Timber.v("[" +
"${if (timelineSettings.isThreadTimeline()) "ThreadTimeLine" else "Timeline"}] Has loaded " +
"${loadedFromStorage.numberOfEvents} items from storage in $direction " +
if (timelineSettings.isThreadTimeline() && loadedFromStorage.threadReachedEnd) "[Reached End]" else "")
+ }
fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? {
val builtEventIndex = builtEventsIndexes[eventId]
@@ -395,7 +395,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
paginationTask.execute(taskParams).toLoadMoreResult()
}
} catch (failure: Throwable) {
- Timber.e("Failed to fetch from server: $failure", failure)
+ Timber.e(failure, "Failed to fetch from server")
LoadMoreResult.FAILURE
}
return if (loadMoreResult == LoadMoreResult.SUCCESS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 640fe53727..99e6521eb7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -354,7 +354,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId)
if (isLimited && lastChunk != null) {
- lastChunk.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true)
+ lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true)
}
val chunkEntity = if (!isLimited && lastChunk != null) {
lastChunk
diff --git a/vector/build.gradle b/vector/build.gradle
index 40c1da0cdc..e533327b82 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -18,7 +18,7 @@ ext.versionMinor = 3
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
-ext.versionPatch = 18
+ext.versionPatch = 19
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index d7e99c63dd..a5962d16fe 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -145,7 +145,7 @@ class ElementRobot {
assertDisplayed(R.string.are_you_sure)
clickOn(R.string.action_skip)
waitUntilViewVisible(withId(R.id.bottomSheetFragmentContainer))
- }.onFailure { Timber.w("Verification popup missing", it) }
+ }.onFailure { Timber.w(it, "Verification popup missing") }
}
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
index 47bf31355c..b3bb5172e8 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
@@ -40,8 +40,11 @@ class OnboardingRobot {
private fun crawlGetStarted() {
clickOn(R.id.loginSplashSubmit)
+ assertDisplayed(R.id.useCaseHeaderTitle, R.string.ftue_auth_use_case_title)
+ clickOn(R.id.useCaseOptionOne)
OnboardingServersRobot().crawlSignUp()
pressBack()
+ pressBack()
}
private fun crawlAlreadyHaveAccount() {
@@ -66,6 +69,7 @@ class OnboardingRobot {
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_create_account)
if (createAccount) {
clickOn(R.id.loginSplashSubmit)
+ clickOn(R.id.useCaseOptionOne)
} else {
clickOn(R.id.loginSplashAlreadyHaveAccount)
}
diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
index 0238931e4c..43283254b1 100644
--- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt
+++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
@@ -16,7 +16,6 @@
package im.vector.app
-import android.content.Context
import android.content.SharedPreferences
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.rageshake.BugReporter
@@ -46,7 +45,6 @@ class AutoRageShaker @Inject constructor(
private val sessionDataSource: ActiveSessionDataSource,
private val activeSessionHolder: ActiveSessionHolder,
private val bugReporter: BugReporter,
- private val context: Context,
private val vectorPreferences: VectorPreferences
) : Session.Listener, SharedPreferences.OnSharedPreferenceChangeListener {
@@ -136,7 +134,6 @@ class AutoRageShaker @Inject constructor(
private fun sendRageShake(target: E2EMessageDetected) {
bugReporter.sendBugReport(
- context = context,
reportType = ReportType.AUTO_UISI,
withDevicesLogs = true,
withCrashLogs = true,
@@ -218,7 +215,6 @@ class AutoRageShaker @Inject constructor(
val matchingIssue = event.content?.get("recipient_rageshake")?.toString() ?: ""
bugReporter.sendBugReport(
- context = context,
reportType = ReportType.AUTO_UISI_SENDER,
withDevicesLogs = true,
withCrashLogs = true,
diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
index d252b5d9bd..e64188765e 100644
--- a/vector/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
@@ -120,7 +120,7 @@ class VectorApplication :
vectorAnalytics.init()
invitesAcceptor.initialize()
autoRageShaker.initialize()
- vectorUncaughtExceptionHandler.activate(this)
+ vectorUncaughtExceptionHandler.activate()
// Remove Log handler statically added by Jitsi
Timber.forest()
diff --git a/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt
new file mode 100644
index 0000000000..5b7988b76f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.datastore
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStoreFile
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Provides a singleton datastore cache
+ * allows for lazily fetching a datastore instance by key to avoid creating multiple stores for the same file
+ * Based on https://androidx.tech/artifacts/datastore/datastore-preferences/1.0.0-source/androidx/datastore/preferences/PreferenceDataStoreDelegate.kt.html
+ *
+ * Makes use of a ReadOnlyProperty in order to provide a simplified api on top of a Context
+ * ReadOnlyProperty allows us to lazily access the backing property instead of requiring it upfront as a dependency
+ *
+ * val Context.dataStoreProvider by dataStoreProvider()
+ *
+ */
+fun dataStoreProvider(): ReadOnlyProperty DataStore> {
+ return MappedPreferenceDataStoreSingletonDelegate()
+}
+
+private class MappedPreferenceDataStoreSingletonDelegate : ReadOnlyProperty DataStore> {
+
+ private val dataStoreCache = ConcurrentHashMap>()
+ private val provider: (Context) -> (String) -> DataStore = { context ->
+ { key ->
+ dataStoreCache.getOrPut(key) {
+ PreferenceDataStoreFactory.create {
+ context.applicationContext.preferencesDataStoreFile(key)
+ }
+ }
+ }
+ }
+
+ override fun getValue(thisRef: Context, property: KProperty<*>) = provider.invoke(thisRef)
+}
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 cce231708a..2cd7136ffc 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
@@ -39,7 +39,6 @@ import im.vector.app.features.discovery.DiscoverySettingsViewModel
import im.vector.app.features.discovery.change.SetIdentityServerViewModel
import im.vector.app.features.home.HomeActivityViewModel
import im.vector.app.features.home.HomeDetailViewModel
-import im.vector.app.features.home.PromoteRestrictedViewModel
import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel
import im.vector.app.features.home.UnreadMessagesSharedViewModel
import im.vector.app.features.home.UserColorAccountDataViewModel
@@ -61,6 +60,7 @@ import im.vector.app.features.login2.created.AccountCreatedViewModel
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
import im.vector.app.features.onboarding.OnboardingViewModel
import im.vector.app.features.poll.create.CreatePollViewModel
+import im.vector.app.features.qrcode.QrCodeScannerViewModel
import im.vector.app.features.rageshake.BugReportViewModel
import im.vector.app.features.reactions.EmojiSearchResultViewModel
import im.vector.app.features.room.RequireActiveMembershipViewModel
@@ -220,6 +220,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(CreateDirectRoomViewModel::class)
fun createDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(QrCodeScannerViewModel::class)
+ fun qrCodeViewModelFactory(factory: QrCodeScannerViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
@Binds
@IntoMap
@MavericksViewModelKey(RoomNotificationSettingsViewModel::class)
@@ -235,11 +240,6 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(SharedSecureStorageViewModel::class)
fun sharedSecureStorageViewModelFactory(factory: SharedSecureStorageViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
- @Binds
- @IntoMap
- @MavericksViewModelKey(PromoteRestrictedViewModel::class)
- fun promoteRestrictedViewModelFactory(factory: PromoteRestrictedViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
-
@Binds
@IntoMap
@MavericksViewModelKey(UserListViewModel::class)
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
index 5295cbaec3..14ba34cc52 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
@@ -76,6 +76,9 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel
+ locationPinProvider?.create(locationOwnerId) { pinDrawable ->
GlideApp.with(holder.staticMapPinImageView)
.load(pinDrawable)
.into(holder.staticMapPinImageView)
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
index aa96a4a30c..829790f857 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
@@ -28,6 +28,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
+import im.vector.app.R
fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
@@ -66,8 +67,12 @@ fun AppCompatActivity.replaceFragment(
fragmentClass: Class,
params: Parcelable? = null,
tag: String? = null,
- allowStateLoss: Boolean = false) {
+ allowStateLoss: Boolean = false,
+ useCustomAnimation: Boolean = false) {
supportFragmentManager.commitTransaction(allowStateLoss) {
+ if (useCustomAnimation) {
+ setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+ }
replace(container.id, fragmentClass, params.toMvRxBundle(), tag)
}
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
index 1063d30a41..b8b367b740 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
@@ -23,7 +23,10 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange
import androidx.core.content.ContextCompat
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
import dagger.hilt.EntryPoints
+import im.vector.app.core.datastore.dataStoreProvider
import im.vector.app.core.di.SingletonEntryPoint
import kotlin.math.roundToInt
@@ -50,3 +53,5 @@ fun Context.getTintedDrawable(@DrawableRes drawableRes: Int,
private fun Float.toAndroidAlpha(): Int {
return (this * 255).roundToInt()
}
+
+val Context.dataStoreProvider: (String) -> DataStore by dataStoreProvider()
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorDummyViewState.kt b/vector/src/main/java/im/vector/app/core/platform/VectorDummyViewState.kt
new file mode 100644
index 0000000000..3c293b1072
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorDummyViewState.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.core.platform
+
+import com.airbnb.mvrx.MavericksState
+
+data class VectorDummyViewState(
+ val isDummy: Unit = Unit
+) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index 03e9954b2c..fe8d58fb51 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -36,5 +36,5 @@ class DefaultVectorFeatures : VectorFeatures {
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = true
- override fun isOnboardingUseCaseEnabled() = false
+ override fun isOnboardingUseCaseEnabled() = true
}
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 5d65d7ea42..3b92e7c4de 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
@@ -17,7 +17,6 @@
package im.vector.app.features.analytics.accountdata
import androidx.lifecycle.asFlow
-import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -26,6 +25,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorDummyViewState
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.log.analyticsTag
@@ -42,24 +42,20 @@ import org.matrix.android.sdk.flow.flow
import timber.log.Timber
import java.util.UUID
-data class DummyState(
- val dummy: Boolean = false
-) : MavericksState
-
class AnalyticsAccountDataViewModel @AssistedInject constructor(
- @Assisted initialState: DummyState,
+ @Assisted initialState: VectorDummyViewState,
private val session: Session,
private val analytics: VectorAnalytics
-) : VectorViewModel(initialState) {
+) : VectorViewModel(initialState) {
private var checkDone: Boolean = false
@AssistedFactory
- interface Factory : MavericksAssistedViewModelFactory {
- override fun create(initialState: DummyState): AnalyticsAccountDataViewModel
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: VectorDummyViewState): AnalyticsAccountDataViewModel
}
- companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
private const val ANALYTICS_EVENT_TYPE = "im.vector.analytics"
}
diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
index d32cef604b..62d360f5f7 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
@@ -179,7 +179,7 @@ class DefaultVectorAnalytics @Inject constructor(
posthog?.identify(REUSE_EXISTING_ID, identity.getProperties().toPostHogProperties(), IGNORED_OPTIONS)
}
- private fun Map?.toPostHogProperties(): Properties? {
+ private fun Map?.toPostHogProperties(): Properties? {
if (this == null) return null
return Properties().apply {
diff --git a/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt b/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
index c6acb3b87a..2797734343 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
@@ -18,5 +18,5 @@ package im.vector.app.features.analytics.itf
interface VectorAnalyticsEvent {
fun getName(): String
- fun getProperties(): Map?
+ fun getProperties(): Map?
}
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
index 1cc433aa7e..99f1fadfc4 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
@@ -55,9 +55,9 @@ data class Identity(
override fun getName() = "Identity"
- override fun getProperties(): Map? {
- return mutableMapOf().apply {
- ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
+ override fun getProperties(): Map? {
+ return mutableMapOf().apply {
+ put("ftueUseCaseSelection", null)
}.takeIf { it.isNotEmpty() }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt
index da3425d326..83c7f0a13b 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt
@@ -23,4 +23,8 @@ sealed class CreateDirectRoomAction : VectorViewModelAction {
data class CreateRoomAndInviteSelectedUsers(
val selections: Set
) : CreateDirectRoomAction()
+
+ data class QrScannedAction(
+ val result: String
+ ) : CreateDirectRoomAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 0df9426852..2d93bab6a3 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
+import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
@@ -44,6 +45,10 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.contactsbook.ContactsBookFragment
+import im.vector.app.features.qrcode.QrCodeScannerEvents
+import im.vector.app.features.qrcode.QrCodeScannerFragment
+import im.vector.app.features.qrcode.QrCodeScannerViewModel
+import im.vector.app.features.qrcode.QrScannerArgs
import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs
import im.vector.app.features.userdirectory.UserListSharedAction
@@ -59,6 +64,8 @@ import javax.inject.Inject
class CreateDirectRoomActivity : SimpleFragmentActivity() {
private val viewModel: CreateDirectRoomViewModel by viewModel()
+ private val qrViewModel: QrCodeScannerViewModel by viewModel()
+
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
@Inject lateinit var errorFormatter: ErrorFormatter
@@ -93,11 +100,38 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
viewModel.onEach(CreateDirectRoomViewState::createAndInviteState) {
renderCreateAndInviteState(it)
}
+
+ viewModel.observeViewEvents {
+ when (it) {
+ CreateDirectRoomViewEvents.InvalidCode -> {
+ Toast.makeText(this, R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ CreateDirectRoomViewEvents.DmSelf -> {
+ Toast.makeText(this, R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ }.exhaustive
+ }
+
+ qrViewModel.observeViewEvents {
+ when (it) {
+ is QrCodeScannerEvents.CodeParsed -> {
+ viewModel.handle(CreateDirectRoomAction.QrScannedAction(it.result))
+ }
+ is QrCodeScannerEvents.ParseFailed -> {
+ Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ else -> Unit
+ }.exhaustive
+ }
}
private fun openAddByQrCode() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
- addFragment(views.container, CreateDirectRoomByQrCodeFragment::class.java)
+ val args = QrScannerArgs(showExtraButtons = false, R.string.add_by_qr_code)
+ addFragment(views.container, QrCodeScannerFragment::class.java, args)
}
}
@@ -118,7 +152,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
- addFragment(views.container, CreateDirectRoomByQrCodeFragment::class.java)
+ addFragment(views.container, QrCodeScannerFragment::class.java)
} else if (deniedPermanently) {
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
deleted file mode 100644
index 766a6f5156..0000000000
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2020 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.createdirect
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import com.airbnb.mvrx.activityViewModel
-import com.google.zxing.Result
-import com.google.zxing.ResultMetadataType
-import im.vector.app.R
-import im.vector.app.core.extensions.hideKeyboard
-import im.vector.app.core.platform.VectorBaseFragment
-import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.checkPermissions
-import im.vector.app.core.utils.onPermissionDeniedDialog
-import im.vector.app.core.utils.registerForPermissionsResult
-import im.vector.app.databinding.FragmentQrCodeScannerBinding
-import im.vector.app.features.userdirectory.PendingSelection
-import me.dm7.barcodescanner.zxing.ZXingScannerView
-import org.matrix.android.sdk.api.session.permalinks.PermalinkData
-import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
-import org.matrix.android.sdk.api.session.user.model.User
-import javax.inject.Inject
-
-class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
-
- private val viewModel: CreateDirectRoomViewModel by activityViewModel()
-
- override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
- return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
- }
-
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
- if (allGranted) {
- startCamera()
- } else if (deniedPermanently) {
- activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
- }
- }
-
- private fun startCamera() {
- // Start camera on resume
- views.scannerView.startCamera()
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- setupToolbar(views.qrScannerToolbar)
- .setTitle(R.string.add_by_qr_code)
- .allowBack(useCross = true)
- }
-
- override fun onResume() {
- super.onResume()
- view?.hideKeyboard()
- // Register ourselves as a handler for scan results.
- views.scannerView.setResultHandler(this)
- // Start camera on resume
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
- startCamera()
- }
- }
-
- override fun onPause() {
- super.onPause()
- // Unregister ourselves as a handler for scan results.
- views.scannerView.setResultHandler(null)
- // Stop camera on pause
- views.scannerView.stopCamera()
- }
-
- // Copied from https://github.com/markusfisch/BinaryEye/blob/
- // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
- private fun getRawBytes(result: Result): ByteArray? {
- val metadata = result.resultMetadata ?: return null
- val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
- var bytes = ByteArray(0)
- @Suppress("UNCHECKED_CAST")
- for (seg in segments as Iterable) {
- bytes += seg
- }
- // byte segments can never be shorter than the text.
- // Zxing cuts off content prefixes like "WIFI:"
- return if (bytes.size >= result.text.length) bytes else null
- }
-
- private fun addByQrCode(value: String) {
- val mxid = (PermalinkParser.parse(value) as? PermalinkData.UserLink)?.userId
-
- if (mxid === null) {
- Toast.makeText(requireContext(), R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
- requireActivity().finish()
- } else {
- // The following assumes MXIDs are case insensitive
- if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
- Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
- requireActivity().finish()
- } else {
- // Try to get user from known users and fall back to creating a User object from MXID
- val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
-
- viewModel.handle(
- CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingSelection.UserPendingSelection(qrInvitee)))
- )
- }
- }
- }
-
- override fun handleResult(result: Result?) {
- if (result === null) {
- Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
- requireActivity().finish()
- } else {
- val rawBytes = getRawBytes(result)
- val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
- val value = rawBytesStr ?: result.text
- addByQrCode(value)
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewEvents.kt
index 0c9804e9a4..060cb0c327 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewEvents.kt
@@ -18,4 +18,7 @@ package im.vector.app.features.createdirect
import im.vector.app.core.platform.VectorViewEvents
-sealed class CreateDirectRoomViewEvents : VectorViewEvents
+sealed class CreateDirectRoomViewEvents : VectorViewEvents {
+ object InvalidCode : CreateDirectRoomViewEvents()
+ object DmSelf : CreateDirectRoomViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
index 41360eab93..9dd3ef6a9b 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
@@ -34,13 +34,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.permalinks.PermalinkData
+import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.user.model.User
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
initialState: CreateDirectRoomViewState,
private val rawService: RawService,
val session: Session) :
- VectorViewModel(initialState) {
+ VectorViewModel(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory {
@@ -51,15 +54,33 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
override fun handle(action: CreateDirectRoomAction) {
when (action) {
- is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action)
+ is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action.selections)
+ is CreateDirectRoomAction.QrScannedAction -> onCodeParsed(action)
}.exhaustive
}
+ private fun onCodeParsed(action: CreateDirectRoomAction.QrScannedAction) {
+ val mxid = (PermalinkParser.parse(action.result) as? PermalinkData.UserLink)?.userId
+
+ if (mxid === null) {
+ _viewEvents.post(CreateDirectRoomViewEvents.InvalidCode)
+ } else {
+ // The following assumes MXIDs are case insensitive
+ if (mxid.equals(other = session.myUserId, ignoreCase = true)) {
+ _viewEvents.post(CreateDirectRoomViewEvents.DmSelf)
+ } else {
+ // Try to get user from known users and fall back to creating a User object from MXID
+ val qrInvitee = if (session.getUser(mxid) != null) session.getUser(mxid)!! else User(mxid, null, null)
+ onSubmitInvitees(setOf(PendingSelection.UserPendingSelection(qrInvitee)))
+ }
+ }
+ }
+
/**
* If users already have a DM room then navigate to it instead of creating a new room.
*/
- private fun onSubmitInvitees(action: CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers) {
- val existingRoomId = action.selections.singleOrNull()?.getMxId()?.let { userId ->
+ private fun onSubmitInvitees(selections: Set) {
+ val existingRoomId = selections.singleOrNull()?.getMxId()?.let { userId ->
session.getExistingDirectRoomWithUser(userId)
}
if (existingRoomId != null) {
@@ -69,7 +90,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}
} else {
// Create the DM
- createRoomAndInviteSelectedUsers(action.selections)
+ createRoomAndInviteSelectedUsers(selections)
}
}
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 b083b74c53..6b6be63480 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
@@ -66,7 +66,6 @@ import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
-import im.vector.app.features.spaces.RestrictedPromoBottomSheet
import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
@@ -111,7 +110,6 @@ class HomeActivity :
private val userColorAccountDataViewModel: UserColorAccountDataViewModel by viewModel()
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
- private val promoteRestrictedViewModel: PromoteRestrictedViewModel by viewModel()
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@@ -267,21 +265,6 @@ class HomeActivity :
shortcutsHandler.observeRoomsAndBuildShortcuts(lifecycleScope)
- if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
- promoteRestrictedViewModel.onEach {
- if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic &&
- it.activeSpaceSummary.otherMemberIds.isNotEmpty()) {
- // It's a private space with some members show this once
- if (it.canUserManageSpace && !popupAlertManager.hasAlertsToShow()) {
- if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
- vectorPreferences.setDidPromoteNewRestrictedFeature()
- RestrictedPromoBottomSheet().show(supportFragmentManager, "RestrictedPromoBottomSheet")
- }
- }
- }
- }
- }
-
if (isFirstCreation()) {
handleIntent(intent)
}
@@ -473,14 +456,14 @@ class HomeActivity :
override fun onResume() {
super.onResume()
- if (vectorUncaughtExceptionHandler.didAppCrash(this)) {
- vectorUncaughtExceptionHandler.clearAppCrashStatus(this)
+ if (vectorUncaughtExceptionHandler.didAppCrash()) {
+ vectorUncaughtExceptionHandler.clearAppCrashStatus()
MaterialAlertDialogBuilder(this)
.setMessage(R.string.send_bug_report_app_crashed)
.setCancelable(false)
.setPositiveButton(R.string.yes) { _, _ -> bugReporter.openBugReportScreen(this) }
- .setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile(this) }
+ .setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile() }
.show()
} else {
showDisclaimerDialog(this)
diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt
deleted file mode 100644
index 5c66e7c52d..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2021 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home
-
-import com.airbnb.mvrx.MavericksState
-import com.airbnb.mvrx.MavericksViewModelFactory
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import im.vector.app.AppStateHandler
-import im.vector.app.RoomGroupingMethod
-import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.core.di.MavericksAssistedViewModelFactory
-import im.vector.app.core.di.hiltMavericksViewModelFactory
-import im.vector.app.core.platform.EmptyAction
-import im.vector.app.core.platform.EmptyViewEvents
-import im.vector.app.core.platform.VectorViewModel
-import kotlinx.coroutines.flow.distinctUntilChanged
-import org.matrix.android.sdk.api.query.QueryStringValue
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
-import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
-
-data class ActiveSpaceViewState(
- val isInSpaceMode: Boolean = false,
- val activeSpaceSummary: RoomSummary? = null,
- val canUserManageSpace: Boolean = false
-) : MavericksState
-
-class PromoteRestrictedViewModel @AssistedInject constructor(
- @Assisted initialState: ActiveSpaceViewState,
- private val activeSessionHolder: ActiveSessionHolder,
- appStateHandler: AppStateHandler
-) : VectorViewModel(initialState) {
-
- init {
- appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().execute { state ->
- val groupingMethod = state.invoke()?.orNull()
- val isSpaceMode = groupingMethod is RoomGroupingMethod.BySpace
- val currentSpace = (groupingMethod as? RoomGroupingMethod.BySpace)?.spaceSummary
- val canManage = currentSpace?.roomId?.let { roomId ->
- activeSessionHolder.getSafeActiveSession()
- ?.getRoom(roomId)
- ?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
- ?.content?.toModel()?.let {
- PowerLevelsHelper(it).isUserAllowedToSend(activeSessionHolder.getActiveSession().myUserId, true, EventType.STATE_SPACE_CHILD)
- } ?: false
- } ?: false
-
- copy(
- isInSpaceMode = isSpaceMode,
- activeSpaceSummary = currentSpace,
- canUserManageSpace = canManage
- )
- }
- }
-
- @AssistedFactory
- interface Factory : MavericksAssistedViewModelFactory {
- override fun create(initialState: ActiveSpaceViewState): PromoteRestrictedViewModel
- }
-
- companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
-
- override fun handle(action: EmptyAction) {}
-}
diff --git a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt
index 3d4f219a7c..37e15af8b3 100644
--- a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt
@@ -16,7 +16,6 @@
package im.vector.app.features.home
-import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -25,6 +24,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorDummyViewState
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import kotlinx.coroutines.flow.launchIn
@@ -37,22 +37,18 @@ import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
import timber.log.Timber
-data class DummyState(
- val dummy: Boolean = false
-) : MavericksState
-
class UserColorAccountDataViewModel @AssistedInject constructor(
- @Assisted initialState: DummyState,
+ @Assisted initialState: VectorDummyViewState,
private val session: Session,
private val matrixItemColorProvider: MatrixItemColorProvider
-) : VectorViewModel(initialState) {
+) : VectorViewModel(initialState) {
@AssistedFactory
- interface Factory : MavericksAssistedViewModelFactory {
- override fun create(initialState: DummyState): UserColorAccountDataViewModel
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: VectorDummyViewState): UserColorAccountDataViewModel
}
- companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
init {
observeAccountData()
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 c63085f647..22d5fc2a77 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
@@ -85,6 +85,8 @@ data class RoomDetailViewState(
fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
+ fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
+
// This checks directly on the active room widgets.
// 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()
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 1deed976bb..2da69bbe6c 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
@@ -88,6 +88,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.lifecycleAwareLazy
import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.core.time.Clock
import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
@@ -253,6 +254,7 @@ class TimelineFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter,
+ private val userPreferencesProvider: UserPreferencesProvider,
private val notificationUtils: NotificationUtils,
private val matrixItemColorProvider: MatrixItemColorProvider,
private val imageContentRenderer: ImageContentRenderer,
@@ -610,13 +612,14 @@ class TimelineFragment @Inject constructor(
}
private fun handleShowLocationPreview(locationContent: MessageLocationContent, senderId: String) {
+ val isSelfLocation = locationContent.isSelfLocation()
navigator
.openLocationSharing(
context = requireContext(),
roomId = timelineArgs.roomId,
mode = LocationSharingMode.PREVIEW,
initialLocationData = locationContent.toLocationData(),
- locationOwnerId = senderId
+ locationOwnerId = if (isSelfLocation) senderId else null
)
}
@@ -1139,16 +1142,12 @@ class TimelineFragment @Inject constructor(
}
private fun handleSearchAction() {
- if (session.getRoom(timelineArgs.roomId)?.isEncrypted() == false) {
- navigator.openSearch(
- context = requireContext(),
- roomId = timelineArgs.roomId,
- roomDisplayName = timelineViewModel.getRoomSummary()?.displayName,
- roomAvatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
- )
- } else {
- showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room))
- }
+ navigator.openSearch(
+ context = requireContext(),
+ roomId = timelineArgs.roomId,
+ roomDisplayName = timelineViewModel.getRoomSummary()?.displayName,
+ roomAvatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
+ )
}
private fun displayDisabledIntegrationDialog() {
@@ -1804,7 +1803,7 @@ class TimelineFragment @Inject constructor(
if (roomId != timelineArgs.roomId) return false
// Navigation to same room
if (!isThreadTimeLine()) {
- if (rootThreadEventId != null) {
+ if (rootThreadEventId != null && userPreferencesProvider.areThreadMessagesEnabled()) {
// Thread link, so PermalinkHandler will handle the navigation
return false
}
@@ -1924,7 +1923,7 @@ class TimelineFragment @Inject constructor(
timelineViewModel.handle(action)
}
is EncryptedEventContent -> {
- timelineViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
+ timelineViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
}
is MessageLocationContent -> {
handleShowLocationPreview(messageContent, informationData.senderId)
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 cc3dabe16b..7d678520ec 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
@@ -720,7 +720,7 @@ class TimelineViewModel @AssistedInject constructor(
R.id.video_call -> state.isWebRTCCallOptionAvailable() || 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.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
- R.id.search -> true
+ R.id.search -> state.isSearchAvailable()
R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled()
R.id.dev_tools -> vectorPreferences.developerMode()
else -> false
@@ -740,14 +740,22 @@ class TimelineViewModel @AssistedInject constructor(
}
private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
- room.undoReaction(action.targetEventId, action.reaction)
+ viewModelScope.launch {
+ tryOrNull {
+ room.undoReaction(action.targetEventId, action.reaction)
+ }
+ }
}
private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) {
if (action.add) {
room.sendReaction(action.targetEventId, action.selectedReaction)
} else {
- room.undoReaction(action.targetEventId, action.selectedReaction)
+ viewModelScope.launch {
+ tryOrNull {
+ room.undoReaction(action.targetEventId, action.selectedReaction)
+ }
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
index 086a093068..27937047a5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
@@ -45,6 +45,7 @@ import im.vector.app.features.location.toLocationData
import im.vector.app.features.media.ImageContentRenderer
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
@@ -77,10 +78,12 @@ class MessageActionsEpoxyController @Inject constructor(
val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL)
val body = state.messageBody.linkify(host.listener)
val bindingOptions = spanUtils.getBindingOptions(body)
- val locationUrl = state.timelineEvent()?.root?.getClearContent()
+
+ val locationContent = state.timelineEvent()?.root?.getClearContent()
?.toModel(catchError = true)
- ?.toLocationData()
+ val locationUrl = locationContent?.toLocationData()
?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
+ val locationOwnerId = if (locationContent?.isSelfLocation().orTrue()) state.informationData.matrixItem.id else null
bottomSheetMessagePreviewItem {
id("preview")
@@ -96,6 +99,7 @@ class MessageActionsEpoxyController @Inject constructor(
time(formattedDate)
locationUrl(locationUrl)
locationPinProvider(host.locationPinProvider)
+ locationOwnerId(locationOwnerId)
}
// Send state
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
index 97f2618fe6..0161f0b55d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
@@ -101,7 +101,11 @@ class CallItemFactory @Inject constructor(
createCallTileTimelineItem(
roomSummary = roomSummary,
callId = callEventGrouper.callId,
- callStatus = if (callEventGrouper.callWasMissed()) CallTileTimelineItem.CallStatus.MISSED else CallTileTimelineItem.CallStatus.ENDED,
+ callStatus = if (callEventGrouper.callWasAnswered()) {
+ CallTileTimelineItem.CallStatus.ENDED
+ } else {
+ CallTileTimelineItem.CallStatus.MISSED
+ },
callKind = callKind,
callback = params.callback,
highlight = params.isHighlighted,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 59b7ba3a8c..77bf5970af 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -219,10 +219,12 @@ class MessageItemFactory @Inject constructor(
urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height)
}
+ val userId = if (locationContent.isSelfLocation()) informationData.senderId else null
+
return MessageLocationItem_()
.attributes(attributes)
.locationUrl(locationUrl)
- .userId(informationData.senderId)
+ .userId(userId)
.locationPinProvider(locationPinProvider)
.highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
index e92376c44d..0cf30c8c01 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
@@ -45,7 +45,17 @@ class LocationPinProvider @Inject constructor(
GlideApp.with(context)
}
- fun create(userId: String, callback: (Drawable) -> Unit) {
+ /**
+ * Creates a pin drawable. If userId is null then a generic pin drawable will be created.
+ * @param userId userId that will be used to retrieve user avatar
+ * @param callback Pin drawable will be sent through the callback
+ */
+ fun create(userId: String?, callback: (Drawable) -> Unit) {
+ if (userId == null) {
+ callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!)
+ return
+ }
+
if (cache.contains(userId)) {
callback(cache[userId]!!)
return
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
index 3910204293..4ff8a9fa43 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
@@ -108,11 +108,8 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) {
}
}
- /**
- * Returns true if there are only events from one side.
- */
- fun callWasMissed(): Boolean {
- return group.events.distinctBy { it.senderInfo.userId }.size == 1
+ fun callWasAnswered(): Boolean {
+ return getAnswer() != null
}
private fun getAnswer(): TimelineEvent? {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
index 6f0b6abb72..607458678e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
@@ -41,14 +41,13 @@ abstract class MessageLocationItem : AbsMessageItem(
renderSendState(holder.view, null)
val location = locationUrl ?: return
- val locationOwnerId = userId ?: return
GlideApp.with(holder.staticMapImageView)
.load(location)
.apply(RequestOptions.centerCropTransform())
.into(holder.staticMapImageView)
- locationPinProvider?.create(locationOwnerId) { pinDrawable ->
+ locationPinProvider?.create(userId) { pinDrawable ->
GlideApp.with(holder.staticMapPinImageView)
.load(pinDrawable)
.into(holder.staticMapPinImageView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt
deleted file mode 100644
index 94a79f5fbd..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.list
-
-import androidx.core.util.Predicate
-import im.vector.app.features.home.RoomListDisplayMode
-import org.matrix.android.sdk.api.session.room.model.Membership
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
-
-class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) : Predicate {
-
- override fun test(roomSummary: RoomSummary): Boolean {
- if (roomSummary.membership.isLeft()) {
- return false
- }
- return when (displayMode) {
- RoomListDisplayMode.NOTIFICATIONS ->
- roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
- RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive()
- RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
- RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/location/Config.kt b/vector/src/main/java/im/vector/app/features/location/Config.kt
index 29ca6b81a9..6f947290e2 100644
--- a/vector/src/main/java/im/vector/app/features/location/Config.kt
+++ b/vector/src/main/java/im/vector/app/features/location/Config.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.location
const val MAP_BASE_URL = "https://api.maptiler.com/maps/streets/style.json"
const val STATIC_MAP_BASE_URL = "https://api.maptiler.com/maps/basic/static/"
+const val DEFAULT_PIN_ID = "DEFAULT_PIN_ID"
const val INITIAL_MAP_ZOOM_IN_PREVIEW = 15.0
const val INITIAL_MAP_ZOOM_IN_TIMELINE = 17.0
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt
index c4f2f148bf..d993c76b0e 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt
@@ -121,7 +121,7 @@ class LocationPreviewFragment @Inject constructor(
MapState(
zoomOnlyOnce = true,
pinLocationData = location,
- pinId = args.locationOwnerId,
+ pinId = args.locationOwnerId ?: DEFAULT_PIN_ID,
pinDrawable = pinDrawable
)
)
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
index 67b36b8442..10c271727b 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
@@ -30,7 +30,7 @@ data class LocationSharingArgs(
val roomId: String,
val mode: LocationSharingMode,
val initialLocationData: LocationData?,
- val locationOwnerId: String
+ val locationOwnerId: String?
) : Parcelable
@AndroidEntryPoint
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
index f6bad2826b..7099bec9f0 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
@@ -118,8 +118,4 @@ class LocationSharingFragment @Inject constructor(
views.mapView.render(state.toMapState())
views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null
}
-
- companion object {
- const val USER_PIN_NAME = "USER_PIN_NAME"
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
index f3b937855a..a9a24094eb 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
@@ -42,6 +42,6 @@ data class LocationSharingViewState(
fun LocationSharingViewState.toMapState() = MapState(
zoomOnlyOnce = true,
pinLocationData = lastKnownLocation,
- pinId = LocationSharingFragment.USER_PIN_NAME,
+ pinId = DEFAULT_PIN_ID,
pinDrawable = pinDrawable
)
diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt
index c56481d3f2..2f71089a39 100644
--- a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt
@@ -41,7 +41,8 @@ class SpaceCardRenderer @Inject constructor(
fun render(spaceSummary: RoomSummary?,
peopleYouKnow: List,
matrixLinkCallback: TimelineEventController.UrlClickCallback?,
- inCard: FragmentMatrixToRoomSpaceCardBinding) {
+ inCard: FragmentMatrixToRoomSpaceCardBinding,
+ showDescription: Boolean) {
if (spaceSummary == null) {
inCard.matrixToCardContentVisibility.isVisible = false
inCard.matrixToCardButtonLoading.isVisible = true
@@ -70,6 +71,8 @@ class SpaceCardRenderer @Inject constructor(
inCard.matrixToMemberPills.isVisible = false
}
+ inCard.matrixToCardDescText.isVisible = showDescription
+
renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() })
}
inCard.matrixToCardDescText.movementMethod = createLinkMovementMethod(object : TimelineEventController.UrlClickCallback {
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 37d459edc2..b521710c1e 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
@@ -556,7 +556,7 @@ class DefaultNavigator @Inject constructor(
roomId: String,
mode: LocationSharingMode,
initialLocationData: LocationData?,
- locationOwnerId: String) {
+ locationOwnerId: String?) {
val intent = LocationSharingActivity.getIntent(
context,
LocationSharingArgs(roomId = roomId, mode = mode, initialLocationData = initialLocationData, locationOwnerId = locationOwnerId)
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 0ba7625aa6..b5e94241ce 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -162,7 +162,8 @@ interface Navigator {
roomId: String,
mode: LocationSharingMode,
initialLocationData: LocationData?,
- locationOwnerId: String)
+ locationOwnerId: String?)
+
fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null)
fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs)
diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
index 87cbf44f04..b67e779a33 100644
--- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
@@ -22,6 +22,7 @@ import androidx.core.net.toUri
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.isIgnored
+import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.core.utils.toast
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.navigation.Navigator
@@ -40,6 +41,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
import javax.inject.Inject
class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
+ private val userPreferencesProvider: UserPreferencesProvider,
private val navigator: Navigator) {
suspend fun launch(
@@ -200,15 +202,17 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
roomSummary: RoomSummary
) {
if (this?.navToRoom(roomId, eventId, rawLink, rootThreadEventId) != true) {
- rootThreadEventId?.let {
+ if (rootThreadEventId != null && userPreferencesProvider.areThreadMessagesEnabled()) {
val threadTimelineArgs = ThreadTimelineArgs(
roomId = roomId,
displayName = roomSummary.displayName,
avatarUrl = roomSummary.avatarUrl,
roomEncryptionTrustLevel = roomSummary.roomEncryptionTrustLevel,
- rootThreadEventId = it)
+ rootThreadEventId = rootThreadEventId)
navigator.openThread(context, threadTimelineArgs, eventId)
- } ?: navigator.openRoom(context, roomId, eventId, buildTask)
+ } else {
+ navigator.openRoom(context, roomId, eventId, buildTask)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerAction.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerAction.kt
new file mode 100644
index 0000000000..910f0246d3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerAction.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 im.vector.app.features.qrcode
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class QrCodeScannerAction : VectorViewModelAction {
+ data class CodeDecoded(
+ val result: String,
+ val isQrCode: Boolean
+ ) : QrCodeScannerAction()
+
+ object ScanFailed : QrCodeScannerAction()
+
+ object SwitchMode : QrCodeScannerAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
index d347bc0250..dda7b2e2eb 100644
--- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
@@ -19,57 +19,55 @@ package im.vector.app.features.qrcode
import android.app.Activity
import android.content.Intent
import android.os.Bundle
+import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
-import com.google.zxing.BarcodeFormat
-import com.google.zxing.Result
-import com.google.zxing.ResultMetadataType
+import com.airbnb.mvrx.viewModel
import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
@AndroidEntryPoint
-class QrCodeScannerActivity : VectorBaseActivity() {
+class QrCodeScannerActivity() : VectorBaseActivity() {
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
+ private val qrViewModel: QrCodeScannerViewModel by viewModel()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ qrViewModel.observeViewEvents {
+ when (it) {
+ is QrCodeScannerEvents.CodeParsed -> {
+ setResultAndFinish(it.result, it.isQrCode)
+ }
+ is QrCodeScannerEvents.ParseFailed -> {
+ Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ else -> Unit
+ }.exhaustive
+ }
+
if (isFirstCreation()) {
- replaceFragment(views.simpleFragmentContainer, QrCodeScannerFragment::class.java)
+ val args = QrScannerArgs(showExtraButtons = false, R.string.verification_scan_their_code)
+ replaceFragment(views.simpleFragmentContainer, QrCodeScannerFragment::class.java, args)
}
}
- fun setResultAndFinish(result: Result?) {
- if (result != null) {
- val rawBytes = getRawBytes(result)
- val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
-
- setResult(RESULT_OK, Intent().apply {
- putExtra(EXTRA_OUT_TEXT, rawBytesStr ?: result.text)
- putExtra(EXTRA_OUT_IS_QR_CODE, result.barcodeFormat == BarcodeFormat.QR_CODE)
- })
- }
+ private fun setResultAndFinish(result: String, isQrCode: Boolean) {
+ setResult(RESULT_OK, Intent().apply {
+ putExtra(EXTRA_OUT_TEXT, result)
+ putExtra(EXTRA_OUT_IS_QR_CODE, isQrCode)
+ })
finish()
}
- // Copied from https://github.com/markusfisch/BinaryEye/blob/
- // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
- private fun getRawBytes(result: Result): ByteArray? {
- val metadata = result.resultMetadata ?: return null
- val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
- var bytes = ByteArray(0)
- @Suppress("UNCHECKED_CAST")
- for (seg in segments as Iterable) {
- bytes += seg
- }
- // byte segments can never be shorter than the text.
- // Zxing cuts off content prefixes like "WIFI:"
- return if (bytes.size >= result.text.length) bytes else null
- }
-
companion object {
private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE"
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerEvents.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerEvents.kt
new file mode 100644
index 0000000000..69a500238e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerEvents.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.features.qrcode
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class QrCodeScannerEvents : VectorViewEvents {
+ data class CodeParsed(val result: String, val isQrCode: Boolean) : QrCodeScannerEvents()
+ object ParseFailed : QrCodeScannerEvents()
+ object SwitchMode : QrCodeScannerEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
index a7231a0c5b..c514a1c8aa 100644
--- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 New Vector Ltd
+ * 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.
@@ -16,50 +16,157 @@
package im.vector.app.features.qrcode
+import android.app.Activity
import android.os.Bundle
+import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.args
+import com.google.zxing.BarcodeFormat
import com.google.zxing.Result
+import com.google.zxing.ResultMetadataType
import im.vector.app.R
+import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding
+import im.vector.app.features.usercode.QRCodeBitmapDecodeHelper
+import im.vector.lib.multipicker.MultiPicker
+import im.vector.lib.multipicker.utils.ImageUtils
+import kotlinx.parcelize.Parcelize
import me.dm7.barcodescanner.zxing.ZXingScannerView
+import org.matrix.android.sdk.api.extensions.tryOrNull
import javax.inject.Inject
-class QrCodeScannerFragment @Inject constructor() :
- VectorBaseFragment(),
- ZXingScannerView.ResultHandler {
+@Parcelize
+data class QrScannerArgs(
+ val showExtraButtons: Boolean,
+ @StringRes val titleRes: Int
+) : Parcelable
+
+open class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
+
+ private val qrViewModel: QrCodeScannerViewModel by activityViewModel()
+ private val scannerArgs: QrScannerArgs? by args()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
}
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ startCamera()
+ } else if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
+ }
+ }
+
+ private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ MultiPicker
+ .get(MultiPicker.IMAGE)
+ .getSelectedFiles(requireActivity(), activityResult.data)
+ .firstOrNull()
+ ?.contentUri
+ ?.let { uri ->
+ // try to see if it is a valid matrix code
+ val bitmap = ImageUtils.getBitmap(requireContext(), uri)
+ ?: return@let Unit.also {
+ Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
+ }
+ handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
+ }
+ }
+ }
+
+ private var autoFocus = true
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ val title = scannerArgs?.titleRes?.let { getString(it) }
+
setupToolbar(views.qrScannerToolbar)
- .setTitle(R.string.verification_scan_their_code)
+ .setTitle(title)
.allowBack(useCross = true)
+
+ scannerArgs?.showExtraButtons?.let { showButtons ->
+ views.userCodeMyCodeButton.isVisible = showButtons
+ views.userCodeOpenGalleryButton.isVisible = showButtons
+
+ if (showButtons) {
+ views.userCodeOpenGalleryButton.debouncedClicks {
+ MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
+ }
+ views.userCodeMyCodeButton.debouncedClicks {
+ qrViewModel.handle(QrCodeScannerAction.SwitchMode)
+ }
+ }
+ }
+ }
+
+ private fun startCamera() {
+ with(views.qrScannerView) {
+ startCamera()
+ setAutoFocus(autoFocus)
+ debouncedClicks {
+ autoFocus = !autoFocus
+ setAutoFocus(autoFocus)
+ }
+ }
}
override fun onResume() {
super.onResume()
+ view?.hideKeyboard()
+
// Register ourselves as a handler for scan results.
- views.scannerView.setResultHandler(this)
- // Start camera on resume
- views.scannerView.startCamera()
+ views.qrScannerView.setResultHandler(this)
+
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
+ startCamera()
+ }
}
override fun onPause() {
super.onPause()
- // Stop camera on pause
- views.scannerView.stopCamera()
+ views.qrScannerView.setResultHandler(null)
+ views.qrScannerView.stopCamera()
+ }
+
+ // Copied from https://github.com/markusfisch/BinaryEye/blob/
+ // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
+ private fun getRawBytes(result: Result): ByteArray? {
+ val metadata = result.resultMetadata ?: return null
+ val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
+ var bytes = ByteArray(0)
+ @Suppress("UNCHECKED_CAST")
+ for (seg in segments as Iterable) {
+ bytes += seg
+ }
+ // byte segments can never be shorter than the text.
+ // Zxing cuts off content prefixes like "WIFI:"
+ return if (bytes.size >= result.text.length) bytes else null
}
override fun handleResult(rawResult: Result?) {
- // Do something with the result here
- // This is not intended to be used outside of QrCodeScannerActivity for the moment
- (requireActivity() as? QrCodeScannerActivity)?.setResultAndFinish(rawResult)
+ if (rawResult == null) {
+ qrViewModel.handle(QrCodeScannerAction.ScanFailed)
+ } else {
+ val rawBytes = getRawBytes(rawResult)
+ val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
+ val result = rawBytesStr ?: rawResult.text
+ val isQrCode = rawResult.barcodeFormat == BarcodeFormat.QR_CODE
+ qrViewModel.handle(QrCodeScannerAction.CodeDecoded(result, isQrCode))
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt
new file mode 100644
index 0000000000..ef47ea1a6e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.qrcode
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+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.VectorViewModel
+import org.matrix.android.sdk.api.session.Session
+
+class QrCodeScannerViewModel @AssistedInject constructor(
+ @Assisted initialState: VectorDummyViewState,
+ val session: Session
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: VectorDummyViewState): QrCodeScannerViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ override fun handle(action: QrCodeScannerAction) {
+ _viewEvents.post(
+ when (action) {
+ is QrCodeScannerAction.CodeDecoded -> QrCodeScannerEvents.CodeParsed(action.result, action.isQrCode)
+ is QrCodeScannerAction.SwitchMode -> QrCodeScannerEvents.SwitchMode
+ is QrCodeScannerAction.ScanFailed -> QrCodeScannerEvents.ParseFailed
+ }
+ )
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
index 0aec24f4ac..2d4bc704a4 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
@@ -151,7 +151,7 @@ class BugReportActivity : VectorBaseActivity() {
views.bugReportProgressView.isVisible = true
views.bugReportProgressView.progress = 0
- bugReporter.sendBugReport(this,
+ bugReporter.sendBugReport(
reportType,
views.bugReportButtonIncludeLogs.isChecked,
views.bugReportButtonIncludeCrashLogs.isChecked,
@@ -249,7 +249,7 @@ class BugReportActivity : VectorBaseActivity() {
override fun onBackPressed() {
// Ensure there is no crash status remaining, which will be sent later on by mistake
- bugReporter.deleteCrashFile(this)
+ bugReporter.deleteCrashFile()
super.onBackPressed()
}
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
index b62a182fd8..2c554716d2 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
@@ -68,6 +68,7 @@ import javax.inject.Singleton
*/
@Singleton
class BugReporter @Inject constructor(
+ private val context: Context,
private val activeSessionHolder: ActiveSessionHolder,
private val versionProvider: VersionProvider,
private val vectorPreferences: VectorPreferences,
@@ -153,7 +154,6 @@ class BugReporter @Inject constructor(
/**
* Send a bug report.
*
- * @param context the application context
* @param reportType The report type (bug, suggestion, feedback)
* @param withDevicesLogs true to include the device log
* @param withCrashLogs true to include the crash logs
@@ -163,8 +163,7 @@ class BugReporter @Inject constructor(
* @param listener the listener
*/
@SuppressLint("StaticFieldLeak")
- fun sendBugReport(context: Context,
- reportType: ReportType,
+ fun sendBugReport(reportType: ReportType,
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
withKeyRequestHistory: Boolean,
@@ -182,7 +181,7 @@ class BugReporter @Inject constructor(
var reportURL: String? = null
withContext(Dispatchers.IO) {
var bugDescription = theBugDescription
- val crashCallStack = getCrashDescription(context)
+ val crashCallStack = getCrashDescription()
if (null != crashCallStack) {
bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
@@ -203,7 +202,7 @@ class BugReporter @Inject constructor(
}
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
- val gzippedLogcat = saveLogCat(context, false)
+ val gzippedLogcat = saveLogCat(false)
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
@@ -213,7 +212,7 @@ class BugReporter @Inject constructor(
}
}
- val crashDescription = getCrashFile(context)
+ val crashDescription = getCrashFile()
if (crashDescription.exists()) {
val compressedCrashDescription = compressFile(crashDescription)
@@ -265,7 +264,7 @@ class BugReporter @Inject constructor(
// build the multi part request
val builder = BugReporterMultipartBody.Builder()
.addFormDataPart("text", text)
- .addFormDataPart("app", rageShakeAppNameForReport(context, reportType))
+ .addFormDataPart("app", rageShakeAppNameForReport(reportType))
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId)
.addFormDataPart("can_contact", canContact.toString())
@@ -352,9 +351,9 @@ class BugReporter @Inject constructor(
}
}
- if (getCrashFile(context).exists()) {
+ if (getCrashFile().exists()) {
builder.addFormDataPart("label", "crash")
- deleteCrashFile(context)
+ deleteCrashFile()
}
val requestBody = builder.build()
@@ -487,20 +486,16 @@ class BugReporter @Inject constructor(
activity.startActivity(BugReportActivity.intent(activity, reportType))
}
- private fun rageShakeAppNameForReport(context: Context, reportType: ReportType): String {
+ private fun rageShakeAppNameForReport(reportType: ReportType): String {
// As per https://github.com/matrix-org/rageshake
// app: Identifier for the application (eg 'riot-web').
// Should correspond to a mapping configured in the configuration file for github issue reporting to work.
// (see R.string.bug_report_url for configured RS server)
- return when (reportType) {
+ return context.getString(when (reportType) {
ReportType.AUTO_UISI_SENDER,
- ReportType.AUTO_UISI -> {
- context.getString(R.string.bug_report_auto_uisi_app_name)
- }
- else -> {
- context.getString(R.string.bug_report_app_name)
- }
- }
+ ReportType.AUTO_UISI -> R.string.bug_report_auto_uisi_app_name
+ else -> R.string.bug_report_app_name
+ })
}
// ==============================================================================================================
// crash report management
@@ -509,20 +504,17 @@ class BugReporter @Inject constructor(
/**
* Provides the crash file
*
- * @param context the context
* @return the crash file
*/
- private fun getCrashFile(context: Context): File {
+ private fun getCrashFile(): File {
return File(context.cacheDir.absolutePath, CRASH_FILENAME)
}
/**
* Remove the crash file
- *
- * @param context
*/
- fun deleteCrashFile(context: Context) {
- val crashFile = getCrashFile(context)
+ fun deleteCrashFile() {
+ val crashFile = getCrashFile()
if (crashFile.exists()) {
crashFile.delete()
@@ -535,11 +527,10 @@ class BugReporter @Inject constructor(
/**
* Save the crash report
*
- * @param context the context
* @param crashDescription teh crash description
*/
- fun saveCrashReport(context: Context, crashDescription: String) {
- val crashFile = getCrashFile(context)
+ fun saveCrashReport(crashDescription: String) {
+ val crashFile = getCrashFile()
if (crashFile.exists()) {
crashFile.delete()
@@ -557,11 +548,10 @@ class BugReporter @Inject constructor(
/**
* Read the crash description file and return its content.
*
- * @param context teh context
* @return the crash description
*/
- private fun getCrashDescription(context: Context): String? {
- val crashFile = getCrashFile(context)
+ private fun getCrashDescription(): String? {
+ val crashFile = getCrashFile()
if (crashFile.exists()) {
try {
@@ -650,11 +640,10 @@ class BugReporter @Inject constructor(
/**
* Save the logcat
*
- * @param context the context
* @param isErrorLogcat true to save the error logcat
* @return the file if the operation succeeds
*/
- private fun saveLogCat(context: Context, isErrorLogcat: Boolean): File? {
+ private fun saveLogCat(isErrorLogcat: Boolean): File? {
val logCatErrFile = File(context.cacheDir.absolutePath, if (isErrorLogcat) LOG_CAT_ERROR_FILENAME else LOG_CAT_FILENAME)
if (logCatErrFile.exists()) {
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
index 6954b9c87b..670b28f1e1 100644
--- a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
@@ -30,9 +30,12 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter,
- private val versionProvider: VersionProvider,
- private val versionCodeProvider: VersionCodeProvider) : Thread.UncaughtExceptionHandler {
+class VectorUncaughtExceptionHandler @Inject constructor(
+ context: Context,
+ private val bugReporter: BugReporter,
+ private val versionProvider: VersionProvider,
+ private val versionCodeProvider: VersionCodeProvider
+) : Thread.UncaughtExceptionHandler {
// key to save the crash status
companion object {
@@ -41,13 +44,12 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
private var previousHandler: Thread.UncaughtExceptionHandler? = null
- private lateinit var context: Context
+ private val preferences = DefaultSharedPreferences.getInstance(context)
/**
* Activate this handler
*/
- fun activate(context: Context) {
- this.context = context
+ fun activate() {
previousHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(this)
}
@@ -61,7 +63,7 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
*/
override fun uncaughtException(thread: Thread, throwable: Throwable) {
Timber.v("Uncaught exception: $throwable")
- DefaultSharedPreferences.getInstance(context).edit {
+ preferences.edit {
putBoolean(PREFS_CRASH_KEY, true)
}
val b = StringBuilder()
@@ -103,7 +105,7 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
val bugDescription = b.toString()
Timber.e("FATAL EXCEPTION $bugDescription")
- bugReporter.saveCrashReport(context, bugDescription)
+ bugReporter.saveCrashReport(bugDescription)
// Show the classical system popup
previousHandler?.uncaughtException(thread, throwable)
@@ -114,16 +116,15 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
*
* @return true if the application crashed
*/
- fun didAppCrash(context: Context): Boolean {
- return DefaultSharedPreferences.getInstance(context)
- .getBoolean(PREFS_CRASH_KEY, false)
+ fun didAppCrash(): Boolean {
+ return preferences.getBoolean(PREFS_CRASH_KEY, false)
}
/**
* Clear the crash status
*/
- fun clearAppCrashStatus(context: Context) {
- DefaultSharedPreferences.getInstance(context).edit {
+ fun clearAppCrashStatus() {
+ preferences.edit {
remove(PREFS_CRASH_KEY)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
index ce85eeeb98..a2f3196979 100644
--- a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
+++ b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
@@ -17,44 +17,42 @@
package im.vector.app.features.session
import android.content.Context
-import androidx.datastore.core.DataStore
-import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
-import androidx.datastore.preferences.preferencesDataStore
+import im.vector.app.core.extensions.dataStoreProvider
import im.vector.app.features.onboarding.FtueUseCase
import kotlinx.coroutines.flow.first
import org.matrix.android.sdk.internal.util.md5
/**
- * Local storage for:
+ * User session scoped storage for:
* - messaging use case (Enum/String)
*/
class VectorSessionStore constructor(
- private val context: Context,
+ context: Context,
myUserId: String
) {
- private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_session_store_${myUserId.md5()}")
private val useCaseKey = stringPreferencesKey("use_case")
+ private val dataStore by lazy { context.dataStoreProvider("vector_session_store_${myUserId.md5()}") }
- suspend fun readUseCase() = context.dataStore.data.first().let { preferences ->
+ suspend fun readUseCase() = dataStore.data.first().let { preferences ->
preferences[useCaseKey]?.let { FtueUseCase.from(it) }
}
suspend fun setUseCase(useCase: FtueUseCase) {
- context.dataStore.edit { settings ->
+ dataStore.edit { settings ->
settings[useCaseKey] = useCase.persistableValue
}
}
suspend fun resetUseCase() {
- context.dataStore.edit { settings ->
+ dataStore.edit { settings ->
settings.remove(useCaseKey)
}
}
suspend fun clear() {
- context.dataStore.edit { settings -> settings.clear() }
+ dataStore.edit { settings -> settings.clear() }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 1903b3776a..f248882211 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -185,7 +185,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY"
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
- private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE"
// Location Sharing
const val SETTINGS_PREF_ENABLE_LOCATION_SHARING = "SETTINGS_PREF_ENABLE_LOCATION_SHARING"
@@ -356,16 +355,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
}
}
- fun didPromoteNewRestrictedFeature(): Boolean {
- return defaultPrefs.getBoolean(DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE, false)
- }
-
- fun setDidPromoteNewRestrictedFeature() {
- defaultPrefs.edit {
- putBoolean(DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE, true)
- }
- }
-
/**
* Tells if we have already asked the user to disable battery optimisations on android >= M devices.
*
diff --git a/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt
deleted file mode 100644
index dbea6807ce..0000000000
--- a/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2021 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.spaces
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.view.isVisible
-import im.vector.app.R
-import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
-import im.vector.app.databinding.BottomSheetSpaceAdvertiseRestrictedBinding
-
-class RestrictedPromoBottomSheet : VectorBaseBottomSheetDialogFragment() {
-
- override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
- BottomSheetSpaceAdvertiseRestrictedBinding.inflate(inflater, container, false)
-
- override val showExpanded = true
-
- var learnMoreMode: Boolean = false
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- render()
- views.skipButton.debouncedClicks {
- dismiss()
- }
-
- views.learnMore.debouncedClicks {
- if (learnMoreMode) {
- dismiss()
- } else {
- learnMoreMode = true
- render()
- }
- }
- }
-
- private fun render() {
- if (learnMoreMode) {
- views.title.text = getString(R.string.new_let_people_in_spaces_find_and_join)
- views.topDescription.text = getString(R.string.to_help_space_members_find_and_join)
- views.imageHint.isVisible = true
- views.bottomDescription.isVisible = true
- views.bottomDescription.text = getString(R.string.this_makes_it_easy_for_rooms_to_stay_private_to_a_space)
- views.skipButton.isVisible = false
- views.learnMore.text = getString(R.string.ok)
- } else {
- views.title.text = getString(R.string.help_space_members)
- views.topDescription.text = getString(R.string.help_people_in_spaces_find_and_join)
- views.imageHint.isVisible = false
- views.bottomDescription.isVisible = false
- views.skipButton.isVisible = true
- views.learnMore.text = getString(R.string.learn_more)
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt
index bbf6ac79ca..955fedd7dc 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt
@@ -170,7 +170,7 @@ class SpaceDirectoryFragment @Inject constructor(
?: getString(R.string.space_explore_activity_title)
}
- spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard)
+ spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard, showDescription = false)
views.addOrCreateChatRoomButton.isVisible = state.canAddRooms
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt
index 815175c977..91cb6194b1 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt
@@ -118,7 +118,7 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment {
val intent = InviteUsersToRoomActivity.getIntent(requireContext(), event.spaceId)
startActivity(intent)
+ dismissAllowingStateLoss()
}
is ShareSpaceViewEvents.ShowInviteByLink -> {
startSharePlainTextIntent(
@@ -94,6 +95,7 @@ class ShareSpaceBottomSheet : VectorBaseBottomSheetDialogFragment(),
- ZXingScannerView.ResultHandler {
-
- override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerWithButtonBinding {
- return FragmentQrCodeScannerWithButtonBinding.inflate(inflater, container, false)
- }
-
- val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
-
- var autoFocus = true
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- setupToolbar(views.qrScannerToolbar)
- .allowBack(useCross = true)
-
- views.userCodeMyCodeButton.debouncedClicks {
- sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
- }
-
- views.userCodeOpenGalleryButton.debouncedClicks {
- MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
- }
- }
-
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
- if (allGranted) {
- startCamera()
- } else {
- // For now just go back
- sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
- }
- }
-
- private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
- if (activityResult.resultCode == Activity.RESULT_OK) {
- MultiPicker
- .get(MultiPicker.IMAGE)
- .getSelectedFiles(requireActivity(), activityResult.data)
- .firstOrNull()
- ?.contentUri
- ?.let { uri ->
- // try to see if it is a valid matrix code
- val bitmap = ImageUtils.getBitmap(requireContext(), uri)
- ?: return@let Unit.also {
- Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
- }
- handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
- }
- }
- }
-
- private fun startCamera() {
- views.userCodeScannerView.startCamera()
- views.userCodeScannerView.setAutoFocus(autoFocus)
- views.userCodeScannerView.debouncedClicks {
- this.autoFocus = !autoFocus
- views.userCodeScannerView.setAutoFocus(autoFocus)
- }
- }
-
- override fun onStart() {
- super.onStart()
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
- startCamera()
- }
- }
-
- override fun onResume() {
- super.onResume()
- // Register ourselves as a handler for scan results.
- views.userCodeScannerView.setResultHandler(this)
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
- startCamera()
- }
- }
-
- override fun onPause() {
- super.onPause()
- views.userCodeScannerView.setResultHandler(null)
- // Stop camera on pause
- views.userCodeScannerView.stopCamera()
- }
-
- override fun handleResult(result: Result?) {
- if (result === null) {
- Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
- requireActivity().finish()
- } else {
- val rawBytes = getRawBytes(result)
- val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
- val value = rawBytesStr ?: result.text
- sharedViewModel.handle(UserCodeActions.DecodedQRCode(value))
- }
- }
-
- // Copied from https://github.com/markusfisch/BinaryEye/blob/
- // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
- private fun getRawBytes(result: Result): ByteArray? {
- val metadata = result.resultMetadata ?: return null
- val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
- var bytes = ByteArray(0)
- @Suppress("UNCHECKED_CAST")
- for (seg in segments as Iterable) {
- bytes += seg
- }
- // byte segments can never be shorter than the text.
- // Zxing cuts off content prefixes like "WIFI:"
- return if (bytes.size >= result.text.length) bytes else null
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
index 7011f8c280..356893aee2 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
@@ -30,12 +30,16 @@ 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.commitTransaction
import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.matrixto.MatrixToBottomSheet
+import im.vector.app.features.qrcode.QrCodeScannerEvents
+import im.vector.app.features.qrcode.QrCodeScannerFragment
+import im.vector.app.features.qrcode.QrCodeScannerViewModel
+import im.vector.app.features.qrcode.QrScannerArgs
import kotlinx.parcelize.Parcelize
import kotlin.reflect.KClass
@@ -44,6 +48,7 @@ class UserCodeActivity : VectorBaseActivity(),
MatrixToBottomSheet.InteractionListener {
val sharedViewModel: UserCodeSharedViewModel by viewModel()
+ private val qrViewModel: QrCodeScannerViewModel by viewModel()
@Parcelize
data class Args(
@@ -81,10 +86,13 @@ class UserCodeActivity : VectorBaseActivity(),
sharedViewModel.onEach(UserCodeState::mode) { mode ->
when (mode) {
- UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
- UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY)
+ UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class)
+ UserCodeState.Mode.SCAN -> {
+ val args = QrScannerArgs(showExtraButtons = true, R.string.user_code_scan)
+ showFragment(QrCodeScannerFragment::class, args)
+ }
is UserCodeState.Mode.RESULT -> {
- showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
+ showFragment(ShowUserCodeFragment::class)
MatrixToBottomSheet.withLink(mode.rawLink).show(supportFragmentManager, "MatrixToBottomSheet")
}
}
@@ -106,6 +114,21 @@ class UserCodeActivity : VectorBaseActivity(),
}
}
}
+
+ qrViewModel.observeViewEvents {
+ when (it) {
+ is QrCodeScannerEvents.CodeParsed -> {
+ sharedViewModel.handle(UserCodeActions.DecodedQRCode(it.result))
+ }
+ QrCodeScannerEvents.SwitchMode -> {
+ sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
+ }
+ is QrCodeScannerEvents.ParseFailed -> {
+ Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ }.exhaustive
+ }
}
override fun onDestroy() {
@@ -113,16 +136,9 @@ class UserCodeActivity : VectorBaseActivity(),
super.onDestroy()
}
- private fun showFragment(fragmentClass: KClass, bundle: Bundle) {
+ private fun showFragment(fragmentClass: KClass, params: Parcelable? = null) {
if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
- supportFragmentManager.commitTransaction {
- setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
- replace(views.simpleFragmentContainer.id,
- fragmentClass.java,
- bundle,
- fragmentClass.simpleName
- )
- }
+ replaceFragment(views.simpleFragmentContainer, fragmentClass.java, params, fragmentClass.simpleName, useCustomAnimation = true)
}
}
diff --git a/vector/src/main/res/drawable-nodpi/room_settings.png b/vector/src/main/res/drawable-nodpi/room_settings.png
deleted file mode 100644
index 2e3fb404fa..0000000000
Binary files a/vector/src/main/res/drawable-nodpi/room_settings.png and /dev/null differ
diff --git a/vector/src/main/res/drawable/ic_location_pin.xml b/vector/src/main/res/drawable/ic_location_pin.xml
new file mode 100644
index 0000000000..8227ea4e05
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_location_pin.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml b/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml
deleted file mode 100644
index 7cc243ee75..0000000000
--- a/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml
index 21a70ded6e..76f29aaab9 100644
--- a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml
+++ b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml
@@ -79,7 +79,7 @@
@@ -19,11 +19,9 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="20dp"
- android:importantForAccessibility="no"
android:elevation="4dp"
+ android:importantForAccessibility="no"
android:transitionName="profile"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/room_round_avatars" />
@@ -31,21 +29,23 @@
+
+
+ app:layout_constraintTop_toBottomOf="@id/matrixToHeaderBarrier">
@@ -21,7 +22,7 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml b/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml
deleted file mode 100644
index 88b07a7655..0000000000
--- a/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/vector/src/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml
index af2319883c..411236a62f 100755
--- a/vector/src/main/res/values/donottranslate.xml
+++ b/vector/src/main/res/values/donottranslate.xml
@@ -16,15 +16,4 @@
Cut the slack from teams.
-
-
- Who will you chat to the most?
- We\'ll help you get connected.
- Friends and family
- Teams
- Communities
- Not sure yet? %s
- You can skip this question
- Looking to join an existing server?
- Connect to server
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index cea9293f06..d34a575037 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1064,6 +1064,7 @@
MESSAGESPEOPLEFILES
+
Searching in encrypted rooms is not supported yet.
@@ -2555,6 +2556,17 @@
${app_name} is also great for the workplace. It’s trusted by the world’s most secure organisations.
+ Who will you chat to the most?
+ We\'ll help you get connected.
+ Friends and family
+ Teams
+ Communities
+
+ Not sure yet? You can %s
+ skip this question
+ Looking to join an existing server?
+ Connect to server
+
It\'s your conversation. Own it.Chat with people directly or in groupsKeep conversations private with encryption
@@ -3672,10 +3684,15 @@
Please note upgrading will make a new version of the room. All current messages will stay in this archived room.
+
New: Let people in spaces find and join private rooms
+
Help people in spaces to find and join private rooms themselves, no need to manually invite everyone.
+
This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.
+
Help space members find private rooms
+
To help space members find and join a private room, go to that room’s settings by tapping on the avatar.
diff --git a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
index fcfff0096f..e273c0b3c9 100644
--- a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
@@ -18,7 +18,11 @@ package im.vector.app.features.location
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull
+import org.amshove.kluent.shouldBeTrue
import org.junit.Test
+import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
+import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
+import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
class LocationDataTest {
@Test
@@ -57,4 +61,16 @@ class LocationDataTest {
parseGeo("ge o:12.34,56.78;13.56").shouldBeNull()
parseGeo("geo :12.34,56.78;13.56").shouldBeNull()
}
+
+ @Test
+ fun selfLocationTest() {
+ val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "", locationAsset = null)
+ contentWithNullAsset.isSelfLocation().shouldBeTrue()
+
+ val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = null))
+ contentWithNullAssetType.isSelfLocation().shouldBeTrue()
+
+ val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = LocationAssetType.SELF))
+ contentWithSelfAssetType.isSelfLocation().shouldBeTrue()
+ }
}