mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-28 05:59:05 +03:00
Merge branch 'release/1.6.12' into main
This commit is contained in:
commit
effdca1832
37 changed files with 203 additions and 107 deletions
17
CHANGES.md
17
CHANGES.md
|
@ -1,3 +1,20 @@
|
||||||
|
Changes in Element v1.6.12 (2024-02-16)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
This update provides important security fixes, please update now.
|
||||||
|
|
||||||
|
Security fixes 🔐
|
||||||
|
-----------------
|
||||||
|
- Add a check on incoming intent. ([#1506 internal](https://github.com/matrix-org/internal-config/issues/1506))
|
||||||
|
- Store temporary files created for Camera in the media folder. ([#1505 internal](https://github.com/matrix-org/internal-config/issues/1505))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Switch the position and styles of the 'already have an account' and 'create account' buttons in the login splash screen. Also changes the 'already have an account one' to just say 'sign in'. ([#+update-login-splash-screen](https://github.com/element-hq/element-android/issues/+update-login-splash-screen))
|
||||||
|
- Improve `Event.getClearContent()` and fix assignment issue that may help to decrypt last Event in the room list. ([#8744](https://github.com/element-hq/element-android/issues/8744))
|
||||||
|
- Fix issues about location Event avatar rendering. ([#8749](https://github.com/element-hq/element-android/issues/8749))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.6.10 (2024-01-09)
|
Changes in Element v1.6.10 (2024-01-09)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Reporting a Vulnerability
|
# Reporting a Vulnerability
|
||||||
|
|
||||||
**If you've found a security vulnerability, please report it to security@matrix.org**
|
**If you've found a security vulnerability in Element software, please report it to security@element.io.**
|
||||||
|
|
||||||
For more information on our security disclosure policy, visit https://www.matrix.org/security-disclosure-policy/
|
For more information on our security disclosure policy, visit https://element.io/security/security-disclosure-policy.
|
||||||
|
|
|
@ -101,7 +101,7 @@ ext.libs = [
|
||||||
],
|
],
|
||||||
element : [
|
element : [
|
||||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||||
'wysiwyg' : "io.element.android:wysiwyg:2.24.0"
|
'wysiwyg' : "io.element.android:wysiwyg:2.29.0"
|
||||||
],
|
],
|
||||||
squareup : [
|
squareup : [
|
||||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
Main changes in this version: add Mobile Device Managament and functional members support.
|
Main changes in this version: add Mobile Device Management and functional members support.
|
||||||
Full changelog: https://github.com/element-hq/element-android/releases
|
Full changelog: https://github.com/element-hq/element-android/releases
|
||||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40106120.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106120.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Security release.
|
||||||
|
Full changelog: https://github.com/element-hq/element-android/releases
|
|
@ -24,7 +24,7 @@ import java.util.Locale
|
||||||
|
|
||||||
internal fun createTemporaryMediaFile(context: Context, mediaType: MediaType): File {
|
internal fun createTemporaryMediaFile(context: Context, mediaType: MediaType): File {
|
||||||
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||||
val storageDir: File = context.filesDir.also { it.mkdirs() }
|
val storageDir: File = File(context.filesDir, "media").also { it.mkdirs() }
|
||||||
val fileSuffix = when (mediaType) {
|
val fileSuffix = when (mediaType) {
|
||||||
MediaType.IMAGE -> ".jpg"
|
MediaType.IMAGE -> ".jpg"
|
||||||
MediaType.VIDEO -> ".mp4"
|
MediaType.VIDEO -> ".mp4"
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
<paths>
|
<paths>
|
||||||
<files-path
|
<files-path
|
||||||
name="external_files"
|
name="external_files"
|
||||||
path="." />
|
path="media" />
|
||||||
</paths>
|
</paths>
|
|
@ -2095,6 +2095,7 @@
|
||||||
<string name="login_splash_text2">Keep conversations private with encryption</string>
|
<string name="login_splash_text2">Keep conversations private with encryption</string>
|
||||||
<string name="login_splash_text3">Extend & customize your experience</string>
|
<string name="login_splash_text3">Extend & customize your experience</string>
|
||||||
<string name="login_splash_submit">Get started</string>
|
<string name="login_splash_submit">Get started</string>
|
||||||
|
<string name="login_splash_sign_in">Sign In</string>
|
||||||
<string name="login_splash_create_account">Create account</string>
|
<string name="login_splash_create_account">Create account</string>
|
||||||
<string name="login_splash_already_have_account">I already have an account</string>
|
<string name="login_splash_already_have_account">I already have an account</string>
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.6.10\""
|
buildConfigField "String", "SDK_VERSION", "\"1.6.12\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a room using the RoomService of a Session.
|
* Get a room using the RoomService of a Session.
|
||||||
|
@ -41,4 +42,5 @@ fun Session.getUser(userId: String): User? = userService().getUser(userId)
|
||||||
/**
|
/**
|
||||||
* Similar to [getUser], but fallback to a User without details if the User is not known by the SDK, or if Session is null.
|
* Similar to [getUser], but fallback to a User without details if the User is not known by the SDK, or if Session is null.
|
||||||
*/
|
*/
|
||||||
fun Session?.getUserOrDefault(userId: String): User = this?.userService()?.getUser(userId) ?: User(userId)
|
fun Session?.getUserOrDefault(userId: String): User = this?.userService()?.getUser(userId)
|
||||||
|
?: User(userId).also { Timber.w("User $userId not found in local cache, fallback to default") }
|
||||||
|
|
|
@ -219,10 +219,16 @@ data class Event(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the event content
|
* @return the event content.
|
||||||
|
* If the content is encrypted, it will return the decrypted content, or null if the content is not
|
||||||
|
* decrypted.
|
||||||
*/
|
*/
|
||||||
fun getClearContent(): Content? {
|
fun getClearContent(): Content? {
|
||||||
return getDecryptedContent() ?: content
|
return if (isEncrypted()) {
|
||||||
|
getDecryptedContent()
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
|
||||||
* Aggregation info concerning a live location share.
|
* Aggregation info concerning a live location share.
|
||||||
*/
|
*/
|
||||||
data class LiveLocationShareAggregatedSummary(
|
data class LiveLocationShareAggregatedSummary(
|
||||||
|
val roomId: String?,
|
||||||
val userId: String?,
|
val userId: String?,
|
||||||
/**
|
/**
|
||||||
* Indicate whether the live is currently running.
|
* Indicate whether the live is currently running.
|
||||||
|
|
|
@ -627,7 +627,7 @@ internal class RustCryptoService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyRoomKeyReceived(
|
private fun notifyRoomKeyReceived(
|
||||||
roomId: String,
|
roomId: String?,
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
) {
|
) {
|
||||||
megolmSessionImportManager.dispatchNewSession(roomId, sessionId)
|
megolmSessionImportManager.dispatchNewSession(roomId, sessionId)
|
||||||
|
@ -664,9 +664,9 @@ internal class RustCryptoService @Inject constructor(
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
EventType.ROOM_KEY -> {
|
EventType.ROOM_KEY -> {
|
||||||
val content = event.getClearContent().toModel<RoomKeyContent>() ?: return@forEach
|
val content = event.getClearContent().toModel<RoomKeyContent>() ?: return@forEach
|
||||||
content.sessionKey
|
|
||||||
val roomId = content.sessionId ?: return@forEach
|
val roomId = content.roomId
|
||||||
val sessionId = content.sessionId
|
val sessionId = content.sessionId ?: return@forEach
|
||||||
|
|
||||||
notifyRoomKeyReceived(roomId, sessionId)
|
notifyRoomKeyReceived(roomId, sessionId)
|
||||||
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.ROOM_KEY)
|
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.ROOM_KEY)
|
||||||
|
@ -674,8 +674,8 @@ internal class RustCryptoService @Inject constructor(
|
||||||
EventType.FORWARDED_ROOM_KEY -> {
|
EventType.FORWARDED_ROOM_KEY -> {
|
||||||
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach
|
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach
|
||||||
|
|
||||||
val roomId = content.sessionId ?: return@forEach
|
val roomId = content.roomId
|
||||||
val sessionId = content.sessionId
|
val sessionId = content.sessionId ?: return@forEach
|
||||||
|
|
||||||
notifyRoomKeyReceived(roomId, sessionId)
|
notifyRoomKeyReceived(roomId, sessionId)
|
||||||
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY)
|
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY)
|
||||||
|
|
|
@ -28,6 +28,7 @@ internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() :
|
||||||
|
|
||||||
override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
|
override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
|
||||||
return LiveLocationShareAggregatedSummary(
|
return LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = entity.roomId,
|
||||||
userId = entity.userId,
|
userId = entity.userId,
|
||||||
isActive = entity.isActive,
|
isActive = entity.isActive,
|
||||||
endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
|
endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
|
||||||
|
private const val ANY_ROOM_ID = "a-room-id"
|
||||||
private const val ANY_USER_ID = "a-user-id"
|
private const val ANY_USER_ID = "a-user-id"
|
||||||
private const val ANY_ACTIVE_STATE = true
|
private const val ANY_ACTIVE_STATE = true
|
||||||
private const val ANY_TIMEOUT = 123L
|
private const val ANY_TIMEOUT = 123L
|
||||||
|
@ -40,6 +41,7 @@ class LiveLocationShareAggregatedSummaryMapperTest {
|
||||||
val summary = mapper.map(entity)
|
val summary = mapper.map(entity)
|
||||||
|
|
||||||
summary shouldBeEqualTo LiveLocationShareAggregatedSummary(
|
summary shouldBeEqualTo LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = ANY_ROOM_ID,
|
||||||
userId = ANY_USER_ID,
|
userId = ANY_USER_ID,
|
||||||
isActive = ANY_ACTIVE_STATE,
|
isActive = ANY_ACTIVE_STATE,
|
||||||
endOfLiveTimestampMillis = ANY_TIMEOUT,
|
endOfLiveTimestampMillis = ANY_TIMEOUT,
|
||||||
|
@ -48,6 +50,7 @@ class LiveLocationShareAggregatedSummaryMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun anEntity(content: MessageBeaconLocationDataContent) = LiveLocationShareAggregatedSummaryEntity(
|
private fun anEntity(content: MessageBeaconLocationDataContent) = LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
roomId = ANY_ROOM_ID,
|
||||||
userId = ANY_USER_ID,
|
userId = ANY_USER_ID,
|
||||||
isActive = ANY_ACTIVE_STATE,
|
isActive = ANY_ACTIVE_STATE,
|
||||||
endOfLiveTimestampMillis = ANY_TIMEOUT,
|
endOfLiveTimestampMillis = ANY_TIMEOUT,
|
||||||
|
|
|
@ -89,7 +89,7 @@ class ValidDecryptedEventTest {
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
|
|
||||||
val unValidatedContent = mixedEvent.getClearContent().toModel<MessageTextContent>()
|
val unValidatedContent = mixedEvent.content.toModel<MessageTextContent>()
|
||||||
unValidatedContent?.body shouldBe "some message"
|
unValidatedContent?.body shouldBe "some message"
|
||||||
|
|
||||||
mixedEvent.toValidDecryptedEvent()?.clearContent?.toModel<MessageTextContent>() shouldBe null
|
mixedEvent.toValidDecryptedEvent()?.clearContent?.toModel<MessageTextContent>() shouldBe null
|
||||||
|
|
|
@ -229,6 +229,7 @@ internal class DefaultLocationSharingServiceTest {
|
||||||
fun `livedata of live summaries is correctly computed`() {
|
fun `livedata of live summaries is correctly computed`() {
|
||||||
val entity = LiveLocationShareAggregatedSummaryEntity()
|
val entity = LiveLocationShareAggregatedSummaryEntity()
|
||||||
val summary = LiveLocationShareAggregatedSummary(
|
val summary = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "",
|
userId = "",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 123,
|
endOfLiveTimestampMillis = 123,
|
||||||
|
@ -255,6 +256,7 @@ internal class DefaultLocationSharingServiceTest {
|
||||||
fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() {
|
fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() {
|
||||||
val entity = LiveLocationShareAggregatedSummaryEntity()
|
val entity = LiveLocationShareAggregatedSummaryEntity()
|
||||||
val summary = LiveLocationShareAggregatedSummary(
|
val summary = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "",
|
userId = "",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 123,
|
endOfLiveTimestampMillis = 123,
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
# Run `pip3 install requests` if not installed yet
|
# Run `pip3 install requests` if not installed yet
|
||||||
import requests
|
import requests
|
||||||
|
# Run `pip3 install re` if not installed yet
|
||||||
|
import re
|
||||||
|
|
||||||
# This script downloads artifacts from GitHub.
|
# This script downloads artifacts from GitHub.
|
||||||
# Ref: https://docs.github.com/en/rest/actions/artifacts#get-an-artifact
|
# Ref: https://docs.github.com/en/rest/actions/artifacts#get-an-artifact
|
||||||
|
@ -65,22 +66,20 @@ if args.verbose:
|
||||||
print(args)
|
print(args)
|
||||||
|
|
||||||
# Split the artifact URL to get information
|
# Split the artifact URL to get information
|
||||||
# Ex: https://github.com/element-hq/element-android/suites/9293388174/artifacts/435942121
|
# Ex: https://github.com/element-hq/element-android/actions/runs/7460386865/artifacts/1156548729
|
||||||
artifactUrl = args.artifactUrl
|
artifactUrl = args.artifactUrl
|
||||||
if not artifactUrl.startswith('https://github.com/'):
|
|
||||||
print("❌ Invalid parameter --artifactUrl %s. Must start with 'https://github.com/'" % artifactUrl)
|
url_regex = r"https://github.com/(.+?)/(.+?)/actions/runs/.+?/artifacts/(.+)"
|
||||||
exit(1)
|
result = re.search(url_regex, artifactUrl)
|
||||||
if "/artifacts/" not in artifactUrl:
|
|
||||||
print("❌ Invalid parameter --artifactUrl %s. Must contain '/artifacts/'" % artifactUrl)
|
if result is None:
|
||||||
exit(1)
|
print(
|
||||||
artifactItems = artifactUrl.split("/")
|
"❌ Invalid parameter --artifactUrl '%s'. Please check the format.\nIt should be something like: %s" %
|
||||||
if len(artifactItems) != 9:
|
(artifactUrl, 'https://github.com/element-hq/element-android/actions/runs/7460386865/artifacts/1156548729')
|
||||||
print("❌ Invalid parameter --artifactUrl %s. Please check the format." % (artifactUrl))
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
gitHubRepoOwner = artifactItems[3]
|
(gitHubRepoOwner, gitHubRepo, artifactId) = result.groups()
|
||||||
gitHubRepo = artifactItems[4]
|
|
||||||
artifactId = artifactItems[8]
|
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print("gitHubRepoOwner: %s, gitHubRepo: %s, artifactId: %s" % (gitHubRepoOwner, gitHubRepo, artifactId))
|
print("gitHubRepoOwner: %s, gitHubRepo: %s, artifactId: %s" % (gitHubRepoOwner, gitHubRepo, artifactId))
|
||||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 6
|
||||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
// 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
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
ext.versionPatch = 10
|
ext.versionPatch = 12
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
|
|
@ -103,10 +103,10 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
.into(holder.staticMapImageView)
|
.into(holder.staticMapImageView)
|
||||||
|
|
||||||
safeLocationUiData.locationPinProvider.create(safeLocationUiData.locationOwnerId) { pinDrawable ->
|
val pinMatrixItem = matrixItem.takeIf { safeLocationUiData.locationOwnerId != null }
|
||||||
GlideApp.with(holder.staticMapPinImageView)
|
safeLocationUiData.locationPinProvider.create(pinMatrixItem) { pinDrawable ->
|
||||||
.load(pinDrawable)
|
// we are not using Glide since it does not display it correctly when there is no user photo
|
||||||
.into(holder.staticMapPinImageView)
|
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.analytics.plan.ViewRoom
|
import im.vector.app.features.analytics.plan.ViewRoom
|
||||||
import im.vector.app.features.home.HomeActivity
|
import im.vector.app.features.home.HomeActivity
|
||||||
import im.vector.app.features.home.ShortcutsHandler
|
import im.vector.app.features.home.ShortcutsHandler
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
|
import im.vector.app.features.home.room.threads.ThreadsActivity
|
||||||
|
import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.pin.UnlockedActivity
|
import im.vector.app.features.pin.UnlockedActivity
|
||||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
||||||
|
@ -115,6 +118,14 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
||||||
putExtra(EXTRA_ROOM_ID, roomId)
|
putExtra(EXTRA_ROOM_ID, roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val allowList = listOf(
|
||||||
|
HomeActivity::class.java.name,
|
||||||
|
MainActivity::class.java.name,
|
||||||
|
RoomDetailActivity::class.java.name,
|
||||||
|
ThreadsActivity::class.java.name,
|
||||||
|
LiveLocationMapViewActivity::class.java.name,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val startAppViewModel: StartAppViewModel by viewModel()
|
private val startAppViewModel: StartAppViewModel by viewModel()
|
||||||
|
@ -186,6 +197,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
||||||
// Start the next Activity
|
// Start the next Activity
|
||||||
startSyncing()
|
startSyncing()
|
||||||
val nextIntent = intent.getParcelableExtraCompat<Intent>(EXTRA_NEXT_INTENT)
|
val nextIntent = intent.getParcelableExtraCompat<Intent>(EXTRA_NEXT_INTENT)
|
||||||
|
?.takeIf { it.isValid() }
|
||||||
startIntentAndFinish(nextIntent)
|
startIntentAndFinish(nextIntent)
|
||||||
} else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
|
} else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
|
||||||
startSyncing()
|
startSyncing()
|
||||||
|
@ -380,4 +392,11 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
||||||
intent?.let { startActivity(it) }
|
intent?.let { startActivity(it) }
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Intent.isValid(): Boolean {
|
||||||
|
val componentName = resolveActivity(packageManager) ?: return false
|
||||||
|
val packageName = componentName.packageName
|
||||||
|
val className = componentName.className
|
||||||
|
return packageName == buildMeta.applicationId && className in allowList
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,7 +238,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
val locationUrl = locationContent.toLocationData()
|
val locationUrl = locationContent.toLocationData()
|
||||||
?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
|
?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
|
||||||
?: return null
|
?: return null
|
||||||
val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.matrixItem.id else null
|
val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.senderId else null
|
||||||
|
|
||||||
return LocationUiData(
|
return LocationUiData(
|
||||||
locationUrl = locationUrl,
|
locationUrl = locationUrl,
|
||||||
|
|
|
@ -114,7 +114,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
|
||||||
.locationUrl(locationUrl)
|
.locationUrl(locationUrl)
|
||||||
.mapWidth(width)
|
.mapWidth(width)
|
||||||
.mapHeight(height)
|
.mapHeight(height)
|
||||||
.locationUserId(attributes.informationData.senderId)
|
.pinMatrixItem(attributes.informationData.matrixItem)
|
||||||
.locationPinProvider(locationPinProvider)
|
.locationPinProvider(locationPinProvider)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
|
|
@ -233,14 +233,14 @@ class MessageItemFactory @Inject constructor(
|
||||||
urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height)
|
urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
val locationUserId = if (locationContent.isSelfLocation()) informationData.senderId else null
|
val pinMatrixItem = if (locationContent.isSelfLocation()) informationData.matrixItem else null
|
||||||
|
|
||||||
return MessageLocationItem_()
|
return MessageLocationItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.locationUrl(locationUrl)
|
.locationUrl(locationUrl)
|
||||||
.mapWidth(width)
|
.mapWidth(width)
|
||||||
.mapHeight(height)
|
.mapHeight(height)
|
||||||
.locationUserId(locationUserId)
|
.pinMatrixItem(pinMatrixItem)
|
||||||
.locationPinProvider(locationPinProvider)
|
.locationPinProvider(locationPinProvider)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
|
|
@ -19,31 +19,33 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import androidx.annotation.ColorInt
|
import android.util.LruCache
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
private data class CachedDrawable(
|
||||||
|
val drawable: Drawable,
|
||||||
|
val isError: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class LocationPinProvider @Inject constructor(
|
class LocationPinProvider @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val matrixItemColorProvider: MatrixItemColorProvider
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
) {
|
) {
|
||||||
private val cache = mutableMapOf<String, Drawable>()
|
private val cache = LruCache<MatrixItem, CachedDrawable>(32)
|
||||||
|
|
||||||
private val glideRequests by lazy {
|
private val glideRequests by lazy {
|
||||||
GlideApp.with(context)
|
GlideApp.with(context)
|
||||||
|
@ -51,32 +53,19 @@ class LocationPinProvider @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a pin drawable. If userId is null then a generic pin drawable will be created.
|
* 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 matrixUser user that will be used to retrieve user avatar
|
||||||
* @param callback Pin drawable will be sent through the callback
|
* @param callback Pin drawable will be sent through the callback
|
||||||
*/
|
*/
|
||||||
fun create(userId: String?, callback: (Drawable) -> Unit) {
|
fun create(matrixUser: MatrixItem?, callback: (Drawable) -> Unit) {
|
||||||
if (userId == null) {
|
if (matrixUser == null) {
|
||||||
callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!)
|
callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cache.contains(userId)) {
|
|
||||||
callback(cache[userId]!!)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
activeSessionHolder
|
|
||||||
.getActiveSession()
|
|
||||||
.getUserOrDefault(userId)
|
|
||||||
.toMatrixItem()
|
|
||||||
.let { userItem ->
|
|
||||||
val size = dimensionConverter.dpToPx(44)
|
val size = dimensionConverter.dpToPx(44)
|
||||||
val bgTintColor = matrixItemColorProvider.getColor(userItem)
|
avatarRenderer.render(glideRequests, matrixUser, object : CustomTarget<Drawable>(size, size) {
|
||||||
avatarRenderer.render(glideRequests, userItem, object : CustomTarget<Drawable>(size, size) {
|
|
||||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||||
Timber.d("## Location: onResourceReady")
|
Timber.d("## Location: onResourceReady")
|
||||||
val pinDrawable = createPinDrawable(resource, bgTintColor)
|
val pinDrawable = createPinDrawable(matrixUser, resource, isError = false)
|
||||||
cache[userId] = pinDrawable
|
|
||||||
callback(pinDrawable)
|
callback(pinDrawable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,17 +76,28 @@ class LocationPinProvider @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
|
// Note: `onLoadFailed` is also called when the user has no avatarUrl
|
||||||
|
// and the errorDrawable is actually the placeholder.
|
||||||
Timber.w("## Location: onLoadFailed")
|
Timber.w("## Location: onLoadFailed")
|
||||||
errorDrawable ?: return
|
errorDrawable ?: return
|
||||||
val pinDrawable = createPinDrawable(errorDrawable, bgTintColor)
|
val pinDrawable = createPinDrawable(matrixUser, errorDrawable, isError = true)
|
||||||
cache[userId] = pinDrawable
|
|
||||||
callback(pinDrawable)
|
callback(pinDrawable)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createPinDrawable(
|
||||||
|
userItem: MatrixItem,
|
||||||
|
drawable: Drawable,
|
||||||
|
isError: Boolean,
|
||||||
|
): Drawable {
|
||||||
|
val fromCache = cache.get(userItem)
|
||||||
|
// Return the cached drawable only if it is valid, or the new drawable is again an error
|
||||||
|
if (fromCache != null && (!fromCache.isError || isError)) {
|
||||||
|
return fromCache.drawable
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPinDrawable(drawable: Drawable, @ColorInt bgTintColor: Int): Drawable {
|
val bgTintColor = matrixItemColorProvider.getColor(userItem)
|
||||||
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
|
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
|
||||||
// use mutate on drawable to avoid sharing the color when we have multiple different user pins
|
// use mutate on drawable to avoid sharing the color when we have multiple different user pins
|
||||||
DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor)
|
DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor)
|
||||||
|
@ -106,6 +106,7 @@ class LocationPinProvider @Inject constructor(
|
||||||
val topInset = dimensionConverter.dpToPx(4)
|
val topInset = dimensionConverter.dpToPx(4)
|
||||||
val bottomInset = dimensionConverter.dpToPx(8)
|
val bottomInset = dimensionConverter.dpToPx(8)
|
||||||
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
|
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
|
||||||
|
cache.put(userItem, CachedDrawable(layerDrawable, isError))
|
||||||
return layerDrawable
|
return layerDrawable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLay
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
||||||
import im.vector.app.features.location.MapLoadingErrorView
|
import im.vector.app.features.location.MapLoadingErrorView
|
||||||
import im.vector.app.features.location.MapLoadingErrorViewState
|
import im.vector.app.features.location.MapLoadingErrorViewState
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
||||||
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
|
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
|
||||||
|
@ -47,7 +48,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
||||||
var locationUrl: String? = null
|
var locationUrl: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var locationUserId: String? = null
|
var pinMatrixItem: MatrixItem? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var mapWidth: Int = 0
|
var mapWidth: Int = 0
|
||||||
|
@ -103,7 +104,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
||||||
dataSource: DataSource?,
|
dataSource: DataSource?,
|
||||||
isFirstResource: Boolean
|
isFirstResource: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
locationPinProvider?.create(locationUserId) { pinDrawable ->
|
locationPinProvider?.create(pinMatrixItem) { pinDrawable ->
|
||||||
// we are not using Glide since it does not display it correctly when there is no user photo
|
// we are not using Glide since it does not display it correctly when there is no user photo
|
||||||
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
||||||
|
|
||||||
private fun bindLiveLocationBanner(holder: Holder) {
|
private fun bindLiveLocationBanner(holder: Holder) {
|
||||||
// TODO in a future PR add check on device id to confirm that is the one that sent the beacon
|
// TODO in a future PR add check on device id to confirm that is the one that sent the beacon
|
||||||
val isEmitter = currentUserId != null && currentUserId == locationUserId
|
val isEmitter = currentUserId != null && currentUserId == pinMatrixItem?.id
|
||||||
val messageLayout = attributes.informationData.messageLayout
|
val messageLayout = attributes.informationData.messageLayout
|
||||||
val viewState = buildViewState(holder, messageLayout, isEmitter)
|
val viewState = buildViewState(holder, messageLayout, isEmitter)
|
||||||
holder.liveLocationRunningBanner.isVisible = true
|
holder.liveLocationRunningBanner.isVisible = true
|
||||||
|
|
|
@ -106,11 +106,13 @@ class LocationSharingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun updatePin(isUserPin: Boolean? = true) {
|
private fun updatePin(isUserPin: Boolean? = true) {
|
||||||
if (isUserPin.orFalse()) {
|
if (isUserPin.orFalse()) {
|
||||||
locationPinProvider.create(userId = session.myUserId) {
|
val matrixItem = room.membershipService().getRoomMember(session.myUserId)?.toMatrixItem()
|
||||||
|
?: session.getUserOrDefault(session.myUserId).toMatrixItem()
|
||||||
|
locationPinProvider.create(matrixItem) {
|
||||||
updatePinDrawableInState(it)
|
updatePinDrawableInState(it)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
locationPinProvider.create(userId = null) {
|
locationPinProvider.create(null) {
|
||||||
updatePinDrawableInState(it)
|
updatePinDrawableInState(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||||
import im.vector.app.features.location.toLocationData
|
import im.vector.app.features.location.toLocationData
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||||
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -43,11 +45,21 @@ class UserLiveLocationViewStateMapper @Inject constructor(
|
||||||
// do nothing on cancellation
|
// do nothing on cancellation
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
locationPinProvider.create(userId) { pinDrawable ->
|
|
||||||
val session = activeSessionHolder.getActiveSession()
|
val session = activeSessionHolder.getActiveSession()
|
||||||
|
val roomId = liveLocationShareAggregatedSummary.roomId
|
||||||
|
val matrixItem = if (roomId != null) {
|
||||||
|
session.getRoom(roomId)
|
||||||
|
?.membershipService()
|
||||||
|
?.getRoomMember(userId)
|
||||||
|
?.toMatrixItem()
|
||||||
|
?: MatrixItem.UserItem(userId)
|
||||||
|
} else {
|
||||||
|
session.getUserOrDefault(userId).toMatrixItem()
|
||||||
|
}
|
||||||
|
locationPinProvider.create(matrixItem) { pinDrawable ->
|
||||||
val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis()
|
val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis()
|
||||||
val viewState = UserLiveLocationViewState(
|
val viewState = UserLiveLocationViewState(
|
||||||
matrixItem = session.getUserOrDefault(userId).toMatrixItem(),
|
matrixItem = matrixItem,
|
||||||
pinDrawable = pinDrawable,
|
pinDrawable = pinDrawable,
|
||||||
locationData = locationData,
|
locationData = locationData,
|
||||||
endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis,
|
endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis,
|
||||||
|
|
|
@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
class LocationPreviewViewModel @AssistedInject constructor(
|
class LocationPreviewViewModel @AssistedInject constructor(
|
||||||
@Assisted private val initialState: LocationPreviewViewState,
|
@Assisted private val initialState: LocationPreviewViewState,
|
||||||
|
@ -46,12 +48,23 @@ class LocationPreviewViewModel @AssistedInject constructor(
|
||||||
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initPin(initialState.pinUserId)
|
val matrixItem = if (initialState.roomId != null && initialState.pinUserId != null) {
|
||||||
|
session
|
||||||
|
.roomService()
|
||||||
|
.getRoom(initialState.roomId)
|
||||||
|
?.membershipService()
|
||||||
|
?.getRoomMember(initialState.pinUserId)
|
||||||
|
?.toMatrixItem()
|
||||||
|
?: MatrixItem.UserItem(initialState.pinUserId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
initPin(matrixItem)
|
||||||
initLocationTracking()
|
initLocationTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initPin(userId: String?) {
|
private fun initPin(matrixItem: MatrixItem?) {
|
||||||
locationPinProvider.create(userId) { pinDrawable ->
|
locationPinProvider.create(matrixItem) { pinDrawable ->
|
||||||
setState { copy(pinDrawable = pinDrawable) }
|
setState { copy(pinDrawable = pinDrawable) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.app.features.location.LocationSharingArgs
|
||||||
|
|
||||||
data class LocationPreviewViewState(
|
data class LocationPreviewViewState(
|
||||||
val pinLocationData: LocationData? = null,
|
val pinLocationData: LocationData? = null,
|
||||||
|
val roomId: String? = null,
|
||||||
val pinUserId: String? = null,
|
val pinUserId: String? = null,
|
||||||
val pinDrawable: Drawable? = null,
|
val pinDrawable: Drawable? = null,
|
||||||
val loadingMapHasFailed: Boolean = false,
|
val loadingMapHasFailed: Boolean = false,
|
||||||
|
@ -32,6 +33,7 @@ data class LocationPreviewViewState(
|
||||||
|
|
||||||
constructor(args: LocationSharingArgs) : this(
|
constructor(args: LocationSharingArgs) : this(
|
||||||
pinLocationData = args.initialLocationData,
|
pinLocationData = args.initialLocationData,
|
||||||
|
roomId = args.roomId,
|
||||||
pinUserId = args.locationOwnerId,
|
pinUserId = args.locationOwnerId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,30 +57,30 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/carouselIndicator" />
|
app:layout_constraintTop_toBottomOf="@id/carouselIndicator" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/loginSplashSubmit"
|
android:id="@+id/loginSplashAlreadyHaveAccount"
|
||||||
style="@style/Widget.Vector.Button.Login"
|
style="@style/Widget.Vector.Button.Login"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login_splash_sign_in"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:transitionName="loginSubmitTransition"
|
android:transitionName="loginSubmitTransition"
|
||||||
app:layout_constraintBottom_toTopOf="@id/loginSplashAlreadyHaveAccount"
|
app:layout_constraintBottom_toTopOf="@id/loginSplashSubmit"
|
||||||
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
||||||
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
||||||
app:layout_constraintTop_toBottomOf="@id/loginSplashButtonsSpace"
|
app:layout_constraintTop_toBottomOf="@id/loginSplashButtonsSpace" />
|
||||||
tools:text="@string/login_splash_create_account" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/loginSplashAlreadyHaveAccount"
|
android:id="@+id/loginSplashSubmit"
|
||||||
style="@style/Widget.Vector.Button.Text.Login"
|
style="@style/Widget.Vector.Button.Text.Login"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/login_splash_already_have_account"
|
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:transitionName="loginSubmitTransition"
|
android:transitionName="loginSubmitTransition"
|
||||||
app:layout_constraintBottom_toTopOf="@id/loginSplashBottomSpace"
|
app:layout_constraintBottom_toTopOf="@id/loginSplashBottomSpace"
|
||||||
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
||||||
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
||||||
app:layout_constraintTop_toBottomOf="@id/loginSplashSubmit" />
|
app:layout_constraintTop_toBottomOf="@id/loginSplashAlreadyHaveAccount"
|
||||||
|
tools:text="@string/login_splash_create_account" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:id="@+id/loginSplashBottomSpace"
|
android:id="@+id/loginSplashBottomSpace"
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.location
|
package im.vector.app.features.location
|
||||||
|
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeFalse
|
||||||
import org.amshove.kluent.shouldBeNull
|
import org.amshove.kluent.shouldBeNull
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -80,6 +81,9 @@ class LocationDataTest {
|
||||||
|
|
||||||
val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF))
|
val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF))
|
||||||
contentWithSelfAssetType.isSelfLocation().shouldBeTrue()
|
contentWithSelfAssetType.isSelfLocation().shouldBeTrue()
|
||||||
|
|
||||||
|
val contentWithPinAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.PIN))
|
||||||
|
contentWithPinAssetType.isSelfLocation().shouldBeFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -54,6 +54,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a room id and event id when calling use case then flow on summary is returned`() = runTest {
|
fun `given a room id and event id when calling use case then flow on summary is returned`() = runTest {
|
||||||
val summary = LiveLocationShareAggregatedSummary(
|
val summary = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "userId",
|
userId = "userId",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 123,
|
endOfLiveTimestampMillis = 123,
|
||||||
|
|
|
@ -59,18 +59,21 @@ class GetListOfUserLiveLocationUseCaseTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a room id then the correct flow of view states list is collected`() = runTest {
|
fun `given a room id then the correct flow of view states list is collected`() = runTest {
|
||||||
val summary1 = LiveLocationShareAggregatedSummary(
|
val summary1 = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "userId1",
|
userId = "userId1",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 123,
|
endOfLiveTimestampMillis = 123,
|
||||||
lastLocationDataContent = MessageBeaconLocationDataContent()
|
lastLocationDataContent = MessageBeaconLocationDataContent()
|
||||||
)
|
)
|
||||||
val summary2 = LiveLocationShareAggregatedSummary(
|
val summary2 = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "userId2",
|
userId = "userId2",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 1234,
|
endOfLiveTimestampMillis = 1234,
|
||||||
lastLocationDataContent = MessageBeaconLocationDataContent()
|
lastLocationDataContent = MessageBeaconLocationDataContent()
|
||||||
)
|
)
|
||||||
val summary3 = LiveLocationShareAggregatedSummary(
|
val summary3 = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
userId = "userId3",
|
userId = "userId3",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = 1234,
|
endOfLiveTimestampMillis = 1234,
|
||||||
|
|
|
@ -72,6 +72,7 @@ class UserLiveLocationViewStateMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a summary with invalid data then result is null`() = runTest {
|
fun `given a summary with invalid data then result is null`() = runTest {
|
||||||
val summary1 = LiveLocationShareAggregatedSummary(
|
val summary1 = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = null,
|
||||||
userId = null,
|
userId = null,
|
||||||
isActive = true,
|
isActive = true,
|
||||||
endOfLiveTimestampMillis = null,
|
endOfLiveTimestampMillis = null,
|
||||||
|
@ -98,17 +99,19 @@ class UserLiveLocationViewStateMapperTest {
|
||||||
unstableTimestampMillis = A_LOCATION_TIMESTAMP
|
unstableTimestampMillis = A_LOCATION_TIMESTAMP
|
||||||
)
|
)
|
||||||
val summary = LiveLocationShareAggregatedSummary(
|
val summary = LiveLocationShareAggregatedSummary(
|
||||||
|
roomId = null,
|
||||||
userId = A_USER_ID,
|
userId = A_USER_ID,
|
||||||
isActive = A_IS_ACTIVE,
|
isActive = A_IS_ACTIVE,
|
||||||
endOfLiveTimestampMillis = A_END_OF_LIVE_TIMESTAMP,
|
endOfLiveTimestampMillis = A_END_OF_LIVE_TIMESTAMP,
|
||||||
lastLocationDataContent = locationDataContent,
|
lastLocationDataContent = locationDataContent,
|
||||||
)
|
)
|
||||||
locationPinProvider.givenCreateForUserId(A_USER_ID, pinDrawable)
|
val matrixItem = MatrixItem.UserItem(id = A_USER_ID, displayName = A_USER_DISPLAY_NAME, avatarUrl = "")
|
||||||
|
locationPinProvider.givenCreateForMatrixItem(matrixItem, pinDrawable)
|
||||||
|
|
||||||
val viewState = userLiveLocationViewStateMapper.map(summary)
|
val viewState = userLiveLocationViewStateMapper.map(summary)
|
||||||
|
|
||||||
val expectedViewState = UserLiveLocationViewState(
|
val expectedViewState = UserLiveLocationViewState(
|
||||||
matrixItem = MatrixItem.UserItem(id = A_USER_ID, displayName = A_USER_DISPLAY_NAME, avatarUrl = ""),
|
matrixItem = matrixItem,
|
||||||
pinDrawable = pinDrawable,
|
pinDrawable = pinDrawable,
|
||||||
locationData = LocationData(
|
locationData = LocationData(
|
||||||
latitude = A_LATITUDE,
|
latitude = A_LATITUDE,
|
||||||
|
|
|
@ -21,12 +21,13 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.invoke
|
import io.mockk.invoke
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
class FakeLocationPinProvider {
|
class FakeLocationPinProvider {
|
||||||
|
|
||||||
val instance = mockk<LocationPinProvider>(relaxed = true)
|
val instance = mockk<LocationPinProvider>(relaxed = true)
|
||||||
|
|
||||||
fun givenCreateForUserId(userId: String, expectedDrawable: Drawable) {
|
fun givenCreateForMatrixItem(matrixItem: MatrixItem, expectedDrawable: Drawable) {
|
||||||
every { instance.create(userId, captureLambda()) } answers { lambda<(Drawable) -> Unit>().invoke(expectedDrawable) }
|
every { instance.create(matrixItem, captureLambda()) } answers { lambda<(Drawable) -> Unit>().invoke(expectedDrawable) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue