diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index cf1cd5b9ff..de434d0122 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -3,14 +3,36 @@
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
-# Build debug version of the RiotX application, from the develop branch and the features branches
-
steps:
- - label: "Assemble GPlay Debug version"
+ - label: "Compile and run Unit tests"
agents:
# We use a medium sized instance instead of the normal small ones because
- # gradle build is long
+ # gradle build can be memory hungry
queue: "medium"
+ commands:
+ - "./gradlew clean test --stacktrace"
+ plugins:
+ - docker#v3.1.0:
+ image: "runmymind/docker-android-sdk"
+ propagate-environment: true
+
+ - label: "Compile Android tests"
+ agents:
+ # We use a medium sized instance instead of the normal small ones because
+ # gradle build can be memory hungry
+ queue: "medium"
+ commands:
+ - "./gradlew clean assembleAndroidTest --stacktrace"
+ plugins:
+ - docker#v3.1.0:
+ image: "runmymind/docker-android-sdk"
+ propagate-environment: true
+
+ - label: "Assemble GPlay Debug version"
+ agents:
+ # We use a xlarge sized instance instead of the normal small ones because
+ # gradle build can be memory hungry
+ queue: "xlarge"
commands:
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
artifact_paths:
@@ -23,9 +45,9 @@ steps:
- label: "Assemble FDroid Debug version"
agents:
- # We use a medium sized instance instead of the normal small ones because
- # gradle build is long
- queue: "medium"
+ # We use a xlarge sized instance instead of the normal small ones because
+ # gradle build can be memory hungry
+ queue: "xlarge"
commands:
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
artifact_paths:
@@ -38,9 +60,9 @@ steps:
- label: "Build Google Play unsigned APK"
agents:
- # We use a medium sized instance instead of the normal small ones because
- # gradle build is long
- queue: "medium"
+ # We use a xlarge sized instance instead of the normal small ones because
+ # gradle build can be memory hungry
+ queue: "xlarge"
commands:
- "./gradlew clean assembleGplayRelease --stacktrace"
artifact_paths:
diff --git a/CHANGES.md b/CHANGES.md
index c52ad4af0d..1a29b77427 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,17 +1,40 @@
-Changes in RiotX 0.7.0 (2019-XX-XX)
+Changes in RiotX 0.8.0 (2019-XX-XX)
+===================================================
+
+Features ✨:
+ -
+
+Improvements 🙌:
+ - Handle code tags (#567)
+
+Other changes:
+ - Markdown set to off by default (#412)
+ - Accessibility improvements to the attachment file type chooser
+
+Bugfix 🐛:
+ - Fix issues with some member events rendering (#498)
+ - Passphrase does not match (Export room keys) (#644)
+ - Ask for permission to write external storage when uri comes from the keyboard (#658)
+
+Translations 🗣:
+ -
+
+Build 🧱:
+ -
+
+Changes in RiotX 0.7.0 (2019-10-24)
===================================================
Features:
- -
+ - Share elements from other app to RiotX (#58)
+ - Read marker (#84)
+ - Add ability to report content (#515)
Improvements:
- Persist active tab between sessions (#503)
- Do not upload file too big for the homeserver (#587)
- - Handle read markers (#84)
- Attachments: start using system pickers (#52)
- - Attachments: start handling incoming share (#58)
- Mark all messages as read (#396)
- - Add ability to report content (#515)
Other changes:
@@ -26,12 +49,6 @@ Bugfix:
- Invitation notifications are not dismissed automatically if room is joined from another client (#347)
- Opening links from RiotX reuses browser tab (#599)
-Translations:
- -
-
-Build:
- -
-
Changes in RiotX 0.6.1 (2019-09-24)
===================================================
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d64dd7110e..45834afa21 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -86,6 +86,10 @@ Also, if possible, please test your change on a real device. Testing on Android
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
Do not hesitate to use plurals when appropriate.
+### Accessibility
+
+Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.
+
### Layout
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle
index 31f928c241..1d8e81e44f 100644
--- a/matrix-sdk-android-rx/build.gradle
+++ b/matrix-sdk-android-rx/build.gradle
@@ -11,6 +11,8 @@ android {
versionCode 1
versionName "1.0"
+ // Multidex is useful for tests
+ multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/matrix-sdk-android-rx/src/androidTest/java/im/vector/matrix/rx/ExampleInstrumentedTest.java b/matrix-sdk-android-rx/src/androidTest/java/im/vector/matrix/rx/ExampleInstrumentedTest.java
deleted file mode 100644
index 986d40d1a9..0000000000
--- a/matrix-sdk-android-rx/src/androidTest/java/im/vector/matrix/rx/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,42 +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.matrix.rx;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
- }
-}
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 3e6d3ea88b..ab5f122dbc 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -155,7 +155,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
- testImplementation 'io.mockk:mockk:1.9.3.kotlin12'
+ // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
+ testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.44'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
@@ -165,7 +166,8 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
- androidTestImplementation 'io.mockk:mockk-android:1.9.3.kotlin12'
+ // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
+ androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt
index 3cd47d4998..99fe7d29b4 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt
@@ -17,12 +17,12 @@
package im.vector.matrix.android
import android.content.Context
-import androidx.test.InstrumentationRegistry
+import androidx.test.core.app.ApplicationProvider
import java.io.File
interface InstrumentedTest {
fun context(): Context {
- return InstrumentationRegistry.getTargetContext()
+ return ApplicationProvider.getApplicationContext()
}
fun cacheDir(): File {
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt
index 7d33fae4d8..5c86f5ad22 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt
@@ -17,8 +17,8 @@
package im.vector.matrix.android.auth
import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
-import androidx.test.runner.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity
import im.vector.matrix.android.api.auth.Authenticator
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt
index ca75871cda..1dbee475e0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt
@@ -16,7 +16,7 @@
package im.vector.matrix.android.api.session.events.model
-import java.util.*
+import java.util.UUID
object LocalEcho {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt
index 43c1544ffd..c05383de4e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt
@@ -62,15 +62,11 @@ data class TimelineEvent(
}
fun getDisambiguatedDisplayName(): String {
- return if (isUniqueDisplayName) {
- senderName
- } else {
- senderName?.let { name ->
- "$name (${root.senderId})"
- }
+ return when {
+ senderName.isNullOrBlank() -> root.senderId ?: ""
+ isUniqueDisplayName -> senderName
+ else -> "$senderName (${root.senderId})"
}
- ?: root.senderId
- ?: ""
}
/**
@@ -104,7 +100,7 @@ fun TimelineEvent.getEditedEventId(): String? {
* Get last MessageContent, after a possible edition
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
- ?: root.getClearContent().toModel()
+ ?: root.getClearContent().toModel()
/**
* Get last Message body, after a possible edition
@@ -113,7 +109,8 @@ fun TimelineEvent.getLastMessageBody(): String? {
val lastMessageContent = getLastMessageContent()
if (lastMessageContent != null) {
- return lastMessageContent.newContent?.toModel()?.body ?: lastMessageContent.body
+ return lastMessageContent.newContent?.toModel()?.body
+ ?: lastMessageContent.body
}
return null
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt
index 7f2a23e4c2..b2002f0916 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt
@@ -66,7 +66,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
if (':' in userId) {
try {
synchronized(notReadyToRetryHS) {
- res = !notReadyToRetryHS.contains(userId.substring(userId.lastIndexOf(":") + 1))
+ res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
}
} catch (e: Exception) {
Timber.e(e, "## canRetryKeysDownload() failed")
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt
index 89a27c9463..86e8a1825c 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt
@@ -216,7 +216,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback {
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
- Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.state)
+ Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}")
} else {
request.state = state
cryptoStore.updateOutgoingRoomKeyRequest(request)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt
index ca1157e583..e0cd47e0e0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt
@@ -43,6 +43,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
+import java.lang.Exception
import java.util.UUID
import javax.inject.Inject
import kotlin.collections.HashMap
@@ -166,72 +167,59 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
return
}
// Download device keys prior to everything
- checkKeysAreDownloaded(
- otherUserId!!,
- startReq,
- success = {
- Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
- val tid = startReq.transactionID!!
- val existing = getExistingTransaction(otherUserId, tid)
- val existingTxs = getExistingTransactionsForUser(otherUserId)
- if (existing != null) {
- // should cancel both!
- Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
- existing.cancel(CancelCode.UnexpectedMessage)
- cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
- } else if (existingTxs?.isEmpty() == false) {
- Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
- // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
- existingTxs.forEach {
- it.cancel(CancelCode.UnexpectedMessage)
- }
- cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
- } else {
- // Ok we can create
- if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
- Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
- val tx = IncomingSASVerificationTransaction(
- this,
- setDeviceVerificationAction,
- credentials,
- cryptoStore,
- sendToDeviceTask,
- taskExecutor,
- myDeviceInfoHolder.get().myDevice.fingerprint()!!,
- startReq.transactionID!!,
- otherUserId)
- addTransaction(tx)
- tx.acceptToDeviceEvent(otherUserId, startReq)
- } else {
- Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
- cancelTransaction(tid, otherUserId, startReq.fromDevice
- ?: event.getSenderKey()!!, CancelCode.UnknownMethod)
- }
- }
- },
- error = {
- cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
- })
+ if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
+ Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
+ val tid = startReq.transactionID!!
+ val existing = getExistingTransaction(otherUserId, tid)
+ val existingTxs = getExistingTransactionsForUser(otherUserId)
+ if (existing != null) {
+ // should cancel both!
+ Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
+ existing.cancel(CancelCode.UnexpectedMessage)
+ cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
+ } else if (existingTxs?.isEmpty() == false) {
+ Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
+ // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
+ existingTxs.forEach {
+ it.cancel(CancelCode.UnexpectedMessage)
+ }
+ cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
+ } else {
+ // Ok we can create
+ if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
+ Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
+ val tx = IncomingSASVerificationTransaction(
+ this,
+ setDeviceVerificationAction,
+ credentials,
+ cryptoStore,
+ sendToDeviceTask,
+ taskExecutor,
+ myDeviceInfoHolder.get().myDevice.fingerprint()!!,
+ startReq.transactionID!!,
+ otherUserId)
+ addTransaction(tx)
+ tx.acceptToDeviceEvent(otherUserId, startReq)
+ } else {
+ Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
+ cancelTransaction(tid, otherUserId, startReq.fromDevice
+ ?: event.getSenderKey()!!, CancelCode.UnknownMethod)
+ }
+ }
+ } else {
+ cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
+ }
}
private suspend fun checkKeysAreDownloaded(otherUserId: String,
- startReq: KeyVerificationStart,
- success: (MXUsersDevicesMap) -> Unit,
- error: () -> Unit) {
- runCatching {
- deviceListManager.downloadKeys(listOf(otherUserId), true)
- }.fold(
- {
- if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
- success(it)
- } else {
- error()
- }
- },
- {
- error()
- }
- )
+ startReq: KeyVerificationStart): MXUsersDevicesMap? {
+ return try {
+ val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
+ val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
+ keys.takeIf { deviceIds.contains(startReq.fromDevice) }
+ } catch (e: Exception) {
+ null
+ }
}
private suspend fun onCancelReceived(event: Event) {
@@ -342,10 +330,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
private fun addTransaction(tx: VerificationTransaction) {
tx.otherUserId.let { otherUserId ->
synchronized(txMap) {
- if (txMap[otherUserId] == null) {
- txMap[otherUserId] = HashMap()
- }
- txMap[otherUserId]?.set(tx.transactionId, tx)
+ val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() }
+ txInnerMap[tx.transactionId] = tx
dispatchTxAdded(tx)
tx.addListener(this)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt
index 24765c120d..36ed2f7edf 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt
@@ -39,14 +39,17 @@ internal fun TimelineEventEntity.updateSenderData() {
val isUnlinked = chunkEntity.isUnlinked()
var senderMembershipEvent: EventEntity?
var senderRoomMemberContent: String?
+ var senderRoomMemberPrevContent: String?
when {
stateIndex <= 0 -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
+ senderRoomMemberPrevContent = senderMembershipEvent?.content
}
else -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
+ senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
}
@@ -58,11 +61,27 @@ internal fun TimelineEventEntity.updateSenderData() {
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.prev(since = stateIndex)
senderRoomMemberContent = senderMembershipEvent?.content
+ senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
+ }
+
+ ContentMapper.map(senderRoomMemberContent).toModel()?.also {
+ this.senderAvatar = it.avatarUrl
+ this.senderName = it.displayName
+ this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
+ }
+
+ // We try to fallback on prev content if we got a room member state events with null fields
+ if (root?.type == EventType.STATE_ROOM_MEMBER) {
+ ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also {
+ if (this.senderAvatar == null && it.avatarUrl != null) {
+ this.senderAvatar = it.avatarUrl
+ }
+ if (this.senderName == null && it.displayName != null) {
+ this.senderName = it.displayName
+ this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
+ }
+ }
}
- val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel()
- this.senderAvatar = senderRoomMember?.avatarUrl
- this.senderName = senderRoomMember?.displayName
- this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
this.senderMembershipEvent = senderMembershipEvent
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
index 5db062b000..0d0143d318 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
@@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt
index bfc37d733d..3d850c223a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt
@@ -22,7 +22,7 @@ import com.novoda.merlin.MerlinsBeard
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import timber.log.Timber
-import java.util.*
+import java.util.Collections
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
index 98ab0b5389..45571286b9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
@@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
-import java.util.*
+import java.util.Date
import javax.inject.Inject
internal interface GetHomeServerCapabilitiesTask : Task
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
index 243e4d4b03..8c7e9fb263 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
@@ -32,7 +32,7 @@ import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
-import java.util.*
+import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
index b50424b343..9fba1d8f02 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
@@ -73,6 +73,7 @@ internal class RoomMembers(private val realm: Realm,
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
+ .isNotNull(EventEntityFields.STATE_KEY)
.distinct(EventEntityFields.STATE_KEY)
.isNotNull(EventEntityFields.CONTENT)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
index 49c813ece6..3fa0dcdca1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
@@ -39,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
-import java.util.*
import javax.inject.Inject
/**
@@ -119,7 +118,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use
permalink,
stringProvider.getString(R.string.message_reply_to_prefix),
userLink,
- originalEvent.senderName ?: originalEvent.root.senderId,
+ originalEvent.getDisambiguatedDisplayName(),
body.takeFormatted(),
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
index 606c20e8cb..4127e43540 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
@@ -52,7 +52,8 @@ import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber
-import java.util.*
+import java.util.Collections
+import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.ArrayList
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/securestorage/SecretStoringUtils.kt
index 260f98d97f..592191975e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/securestorage/SecretStoringUtils.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/securestorage/SecretStoringUtils.kt
@@ -31,7 +31,7 @@ import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.SecureRandom
-import java.util.*
+import java.util.Calendar
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CompatUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CompatUtil.kt
index 058a862bc8..2df2bd2bf2 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CompatUtil.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CompatUtil.kt
@@ -36,7 +36,7 @@ import java.security.*
import java.security.cert.CertificateException
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
-import java.util.*
+import java.util.Calendar
import java.util.zip.GZIPOutputStream
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt
index 4a46a43f03..31da372bbe 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt
@@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.util
import im.vector.matrix.android.api.MatrixPatterns
import timber.log.Timber
-import java.util.*
+import java.util.Locale
/**
* Convert a string to an UTF8 String
diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt
index f98af53333..17543e9d25 100644
--- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt
+++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt
@@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.di.MoshiProvider
-import org.junit.Assert
+import org.junit.Assert.*
import org.junit.Test
class PushRuleActionsTest {
@@ -63,22 +63,17 @@ class PushRuleActionsTest {
val pushRule = MoshiProvider.providesMoshi().adapter(PushRule::class.java).fromJson(rawPushRule)
- Assert.assertNotNull("Should have parsed the rule", pushRule)
- Assert.assertNotNull("Failed to parse actions", Action.mapFrom(pushRule!!))
+ assertNotNull("Should have parsed the rule", pushRule)
- val actions = Action.mapFrom(pushRule)
- Assert.assertEquals(3, actions!!.size)
+ val actions = pushRule!!.getActions()
+ assertEquals(3, actions.size)
- Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, actions[0].type)
+ assertTrue("First action should be notify", actions[0] is Action.Notify)
- Assert.assertEquals("Second action should be tweak", Action.Type.SET_TWEAK, actions[1].type)
- Assert.assertEquals("Second action tweak key should be sound", "sound", actions[1].tweak_action)
- Assert.assertEquals("Second action should have default as stringValue", "default", actions[1].stringValue)
- Assert.assertNull("Second action boolValue should be null", actions[1].boolValue)
+ assertTrue("Second action should be sound", actions[1] is Action.Sound)
+ assertEquals("Second action should have default sound", "default", (actions[1] as Action.Sound).sound)
- Assert.assertEquals("Third action should be tweak", Action.Type.SET_TWEAK, actions[2].type)
- Assert.assertEquals("Third action tweak key should be highlight", "highlight", actions[2].tweak_action)
- Assert.assertEquals("Third action tweak param should be false", false, actions[2].boolValue)
- Assert.assertNull("Third action stringValue should be null", actions[2].stringValue)
+ assertTrue("Third action should be highlight", actions[2] is Action.Highlight)
+ assertEquals("Third action tweak param should be false", false, (actions[2] as Action.Highlight).highlight)
}
}
diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
index 42e7e850b3..7651b32d20 100644
--- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
@@ -199,6 +199,10 @@ class PushrulesConditionTest {
}
class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room {
+ override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): Cancelable {
+ TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
+ }
+
override fun getReadMarkerLive(): LiveData> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
diff --git a/tools/import_from_riot.sh b/tools/import_from_riot.sh
index 2e4b332a3c..3f93615d19 100755
--- a/tools/import_from_riot.sh
+++ b/tools/import_from_riot.sh
@@ -102,6 +102,7 @@ cp ../riot-android/vector/src/main/res/values-ro/strings.xml ./vector/src
cp ../riot-android/vector/src/main/res/values-ru/strings.xml ./vector/src/main/res/values-ru/strings.xml
cp ../riot-android/vector/src/main/res/values-sk/strings.xml ./vector/src/main/res/values-sk/strings.xml
cp ../riot-android/vector/src/main/res/values-sq/strings.xml ./vector/src/main/res/values-sq/strings.xml
+cp ../riot-android/vector/src/main/res/values-sr/strings.xml ./vector/src/main/res/values-sr/strings.xml
cp ../riot-android/vector/src/main/res/values-te/strings.xml ./vector/src/main/res/values-te/strings.xml
cp ../riot-android/vector/src/main/res/values-th/strings.xml ./vector/src/main/res/values-th/strings.xml
cp ../riot-android/vector/src/main/res/values-tlh/strings.xml ./vector/src/main/res/values-tlh/strings.xml
diff --git a/vector/build.gradle b/vector/build.gradle
index 3ef125d331..d639b4c3e8 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -15,7 +15,7 @@ androidExtensions {
}
ext.versionMajor = 0
-ext.versionMinor = 7
+ext.versionMinor = 8
ext.versionPatch = 0
static def getGitTimestamp() {
@@ -219,7 +219,7 @@ dependencies {
def epoxy_version = '3.8.0'
def arrow_version = "0.8.2"
def coroutines_version = "1.3.2"
- def markwon_version = '3.1.0'
+ def markwon_version = '4.1.2'
def big_image_viewer_version = '1.5.6'
def glide_version = '4.10.0'
def moshi_version = '1.8.0'
@@ -283,8 +283,8 @@ dependencies {
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'me.gujun.android:span:1.7'
- implementation "ru.noties.markwon:core:$markwon_version"
- implementation "ru.noties.markwon:html:$markwon_version"
+ implementation "io.noties.markwon:core:$markwon_version"
+ implementation "io.noties.markwon:html:$markwon_version"
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
diff --git a/vector/src/androidTest/java/im/vector/riotx/ExampleInstrumentedTest.kt b/vector/src/androidTest/java/im/vector/riotx/ExampleInstrumentedTest.kt
deleted file mode 100644
index afed0c783a..0000000000
--- a/vector/src/androidTest/java/im/vector/riotx/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,40 +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.riotx
-
-import androidx.test.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getTargetContext()
- assertEquals("im.vector.riotx", appContext.packageName)
- }
-}
diff --git a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
index d0301e2c9f..76cbb9ef94 100644
--- a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
@@ -42,7 +42,7 @@ import javax.inject.Singleton
@Singleton
class AppStateHandler @Inject constructor(
private val sessionObservableStore: ActiveSessionObservableStore,
- private val homeRoomListStore: HomeRoomListObservableStore,
+ private val homeRoomListObservableStore: HomeRoomListObservableStore,
private val selectedGroupStore: SelectedGroupStore) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable()
@@ -92,7 +92,7 @@ class AppStateHandler @Inject constructor(
}
)
.subscribe {
- homeRoomListStore.post(it)
+ homeRoomListObservableStore.post(it)
}
.addTo(compositeDisposable)
}
diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt
index b1fd6a8485..20a17e55d4 100644
--- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt
@@ -55,7 +55,8 @@ import im.vector.riotx.features.version.VersionProvider
import im.vector.riotx.push.fcm.FcmHelper
import timber.log.Timber
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
import javax.inject.Inject
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
index cb9dcf375e..87ed61c695 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
@@ -41,12 +41,13 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFrag
import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
-import im.vector.riotx.features.home.room.detail.timeline.action.*
+import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.room.list.RoomListFragment
+import im.vector.riotx.features.home.room.list.RoomListModule
import im.vector.riotx.features.invite.VectorInviteView
import im.vector.riotx.features.link.LinkHandlerActivity
import im.vector.riotx.features.login.LoginActivity
@@ -71,7 +72,17 @@ import im.vector.riotx.features.settings.push.PushGatewaysFragment
import im.vector.riotx.features.share.IncomingShareActivity
import im.vector.riotx.features.ui.UiStateRepository
-@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class])
+@Component(
+ dependencies = [
+ VectorComponent::class
+ ],
+ modules = [
+ AssistedInjectModule::class,
+ ViewModelModule::class,
+ HomeModule::class,
+ RoomListModule::class
+ ]
+)
@ScreenScope
interface ScreenComponent {
diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
index a59620aacb..2dfbb5f799 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
@@ -23,6 +23,7 @@ import dagger.Component
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session
+import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.EmojiCompatWrapper
import im.vector.riotx.VectorApplication
@@ -42,6 +43,7 @@ import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.settings.VectorPreferences
+import im.vector.riotx.features.share.ShareRoomListObservableStore
import im.vector.riotx.features.ui.UiStateRepository
import javax.inject.Singleton
@@ -85,8 +87,12 @@ interface VectorComponent {
fun homeRoomListObservableStore(): HomeRoomListObservableStore
+ fun shareRoomListObservableStore(): ShareRoomListObservableStore
+
fun selectedGroupStore(): SelectedGroupStore
+ fun activeSessionObservableStore(): ActiveSessionObservableStore
+
fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler
fun incomingKeyRequestHandler(): KeyRequestHandler
diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/ExportKeysDialog.kt
index fb320afded..1cb6c5406a 100644
--- a/vector/src/main/java/im/vector/riotx/core/dialogs/ExportKeysDialog.kt
+++ b/vector/src/main/java/im/vector/riotx/core/dialogs/ExportKeysDialog.kt
@@ -44,15 +44,15 @@ class ExportKeysDialog {
val textWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
when {
- passPhrase1EditText.text.isNullOrEmpty() -> {
+ passPhrase1EditText.text.isNullOrEmpty() -> {
exportButton.isEnabled = false
passPhrase2Til.error = null
}
- passPhrase1EditText.text == passPhrase2EditText.text -> {
+ passPhrase1EditText.text.toString() == passPhrase2EditText.text.toString() -> {
exportButton.isEnabled = true
passPhrase2Til.error = null
}
- else -> {
+ else -> {
exportButton.isEnabled = false
passPhrase2Til.error = activity.getString(R.string.passphrase_passphrase_does_not_match)
}
diff --git a/vector/src/main/java/im/vector/riotx/core/files/FileSaver.kt b/vector/src/main/java/im/vector/riotx/core/files/FileSaver.kt
index c1f58306a4..677f7894e8 100644
--- a/vector/src/main/java/im/vector/riotx/core/files/FileSaver.kt
+++ b/vector/src/main/java/im/vector/riotx/core/files/FileSaver.kt
@@ -30,15 +30,10 @@ import java.io.File
*/
@WorkerThread
fun writeToFile(str: String, file: File): Try {
- return Try {
- val sink = file.sink()
-
- val bufferedSink = sink.buffer()
-
- bufferedSink.writeString(str, Charsets.UTF_8)
-
- bufferedSink.close()
- sink.close()
+ return Try {
+ file.sink().buffer().use {
+ it.writeString(str, Charsets.UTF_8)
+ }
}
}
@@ -47,15 +42,10 @@ fun writeToFile(str: String, file: File): Try {
*/
@WorkerThread
fun writeToFile(data: ByteArray, file: File): Try {
- return Try {
- val sink = file.sink()
-
- val bufferedSink = sink.buffer()
-
- bufferedSink.write(data)
-
- bufferedSink.close()
- sink.close()
+ return Try {
+ file.sink().buffer().use {
+ it.write(data)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt
index b6ae2be20b..84cba7392f 100644
--- a/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt
+++ b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt
@@ -17,7 +17,6 @@
package im.vector.riotx.core.images
import android.content.Context
-import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import androidx.exifinterface.media.ExifInterface
@@ -37,26 +36,24 @@ class ImageTools @Inject constructor(private val context: Context) {
if (uri.scheme == "content") {
val proj = arrayOf(MediaStore.Images.Media.DATA)
- var cursor: Cursor? = null
try {
- cursor = context.contentResolver.query(uri, proj, null, null, null)
- if (cursor != null && cursor.count > 0) {
- cursor.moveToFirst()
- val idxData = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
- val path = cursor.getString(idxData)
- if (path.isNullOrBlank()) {
- Timber.w("Cannot find path in media db for uri $uri")
- return orientation
+ val cursor = context.contentResolver.query(uri, proj, null, null, null)
+ cursor?.use {
+ if (it.moveToFirst()) {
+ val idxData = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ val path = it.getString(idxData)
+ if (path.isNullOrBlank()) {
+ Timber.w("Cannot find path in media db for uri $uri")
+ return orientation
+ }
+ val exif = ExifInterface(path)
+ orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
}
- val exif = ExifInterface(path)
- orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
}
} catch (e: Exception) {
// eg SecurityException from com.google.android.apps.photos.content.GooglePhotosImageProvider URIs
// eg IOException from trying to parse the returned path as a file when it is an http uri.
Timber.e(e, "Cannot get orientation for bitmap")
- } finally {
- cursor?.close()
}
} else if (uri.scheme == "file") {
try {
diff --git a/vector/src/main/java/im/vector/riotx/core/intent/Filename.kt b/vector/src/main/java/im/vector/riotx/core/intent/Filename.kt
index 9e9f0ae508..2b6740f62f 100644
--- a/vector/src/main/java/im/vector/riotx/core/intent/Filename.kt
+++ b/vector/src/main/java/im/vector/riotx/core/intent/Filename.kt
@@ -17,28 +17,17 @@
package im.vector.riotx.core.intent
import android.content.Context
-import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns
fun getFilenameFromUri(context: Context?, uri: Uri): String? {
- var result: String? = null
if (context != null && uri.scheme == "content") {
- val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
- try {
- if (cursor != null && cursor.moveToFirst()) {
- result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+ val cursor = context.contentResolver.query(uri, null, null, null, null)
+ cursor?.use {
+ if (it.moveToFirst()) {
+ return it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
- } finally {
- cursor?.close()
}
}
- if (result == null) {
- result = uri.path
- val cut = result?.lastIndexOf('/') ?: -1
- if (cut != -1) {
- result = result?.substring(cut + 1)
- }
- }
- return result
+ return uri.path?.substringAfterLast('/')
}
diff --git a/vector/src/main/java/im/vector/riotx/core/linkify/VectorAutoLinkPatterns.kt b/vector/src/main/java/im/vector/riotx/core/linkify/VectorAutoLinkPatterns.kt
index e6eb886e02..ae4131b5e9 100644
--- a/vector/src/main/java/im/vector/riotx/core/linkify/VectorAutoLinkPatterns.kt
+++ b/vector/src/main/java/im/vector/riotx/core/linkify/VectorAutoLinkPatterns.kt
@@ -15,8 +15,6 @@
*/
package im.vector.riotx.core.linkify
-import java.util.regex.Pattern
-
/**
* Better support for geo URi
*/
@@ -26,7 +24,7 @@ object VectorAutoLinkPatterns {
private const val LAT_OR_LONG_OR_ALT_NUMBER = "-?\\d+(?:\\.\\d+)?"
private const val COORDINATE_SYSTEM = ";crs=[\\w-]+"
- val GEO_URI: Pattern = Pattern.compile("(?:geo:)?" +
+ val GEO_URI: Regex = Regex("(?:geo:)?" +
"(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" +
"," +
"(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" +
@@ -35,5 +33,5 @@ object VectorAutoLinkPatterns {
"(?:" + ";u=\\d+(?:\\.\\d+)?" + ")?" + // uncertainty in meters
"(?:" +
";[\\w-]+=(?:[\\w-_.!~*'()]|%[\\da-f][\\da-f])+" + // dafuk
- ")*", Pattern.CASE_INSENSITIVE)
+ ")*", RegexOption.IGNORE_CASE)
}
diff --git a/vector/src/main/java/im/vector/riotx/core/linkify/VectorLinkify.kt b/vector/src/main/java/im/vector/riotx/core/linkify/VectorLinkify.kt
index 358fff6092..99b0316cbe 100644
--- a/vector/src/main/java/im/vector/riotx/core/linkify/VectorLinkify.kt
+++ b/vector/src/main/java/im/vector/riotx/core/linkify/VectorLinkify.kt
@@ -19,7 +19,6 @@ import android.text.Spannable
import android.text.style.URLSpan
import android.text.util.Linkify
import androidx.core.text.util.LinkifyCompat
-import java.util.*
object VectorLinkify {
/**
@@ -95,7 +94,7 @@ object VectorLinkify {
createdSpans.add(LinkSpec(URLSpan(urlSpan.url), start, end))
}
- LinkifyCompat.addLinks(spannable, VectorAutoLinkPatterns.GEO_URI, "geo:", arrayOf("geo:"), geoMatchFilter, null)
+ LinkifyCompat.addLinks(spannable, VectorAutoLinkPatterns.GEO_URI.toPattern(), "geo:", arrayOf("geo:"), geoMatchFilter, null)
spannable.forEachSpanIndexed { _, urlSpan, start, end ->
spannable.removeSpan(urlSpan)
createdSpans.add(LinkSpec(URLSpan(urlSpan.url), start, end))
@@ -108,7 +107,7 @@ object VectorLinkify {
}
private fun pruneOverlaps(links: ArrayList) {
- Collections.sort(links, COMPARATOR)
+ links.sortWith(COMPARATOR)
var len = links.size
var i = 0
while (i < len - 1) {
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/MaxHeightScrollView.kt b/vector/src/main/java/im/vector/riotx/core/platform/MaxHeightScrollView.kt
index fc09ad0f75..b8587750a3 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/MaxHeightScrollView.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/MaxHeightScrollView.kt
@@ -16,17 +16,15 @@
package im.vector.riotx.core.platform
-import android.annotation.TargetApi
import android.content.Context
-import android.os.Build
import android.util.AttributeSet
-import android.widget.ScrollView
-
+import androidx.core.widget.NestedScrollView
import im.vector.riotx.R
private const val DEFAULT_MAX_HEIGHT = 200
-class MaxHeightScrollView : ScrollView {
+class MaxHeightScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
+ : NestedScrollView(context, attrs, defStyle) {
var maxHeight: Int = 0
set(value) {
@@ -34,28 +32,7 @@ class MaxHeightScrollView : ScrollView {
requestLayout()
}
- constructor(context: Context) : super(context) {}
-
- constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
- if (!isInEditMode) {
- init(context, attrs)
- }
- }
-
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
- if (!isInEditMode) {
- init(context, attrs)
- }
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
- if (!isInEditMode) {
- init(context, attrs)
- }
- }
-
- private fun init(context: Context, attrs: AttributeSet?) {
+ init {
if (attrs != null) {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView)
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, DEFAULT_MAX_HEIGHT)
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt
index 8d40d55a7a..1b07d739b5 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -30,7 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.utils.DimensionConverter
-import java.util.*
+import java.util.UUID
/**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
diff --git a/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
index 41287d4e38..e2c08a1fe8 100644
--- a/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
@@ -22,8 +22,9 @@ import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.resources.AppNameProvider
import im.vector.riotx.core.resources.LocaleProvider
import im.vector.riotx.core.resources.StringProvider
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
+import kotlin.math.abs
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
@@ -36,7 +37,7 @@ class PushersManager @Inject constructor(
fun registerPusherWithFcmKey(pushKey: String): UUID {
val currentSession = activeSessionHolder.getActiveSession()
- var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.myUserId.hashCode())
+ val profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(currentSession.myUserId.hashCode())
return currentSession.addHttpPusher(
pushKey,
diff --git a/vector/src/main/java/im/vector/riotx/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/riotx/core/resources/LocaleProvider.kt
index 74861a65cc..c78a5a99b8 100644
--- a/vector/src/main/java/im/vector/riotx/core/resources/LocaleProvider.kt
+++ b/vector/src/main/java/im/vector/riotx/core/resources/LocaleProvider.kt
@@ -18,7 +18,7 @@ package im.vector.riotx.core.resources
import android.content.res.Resources
import androidx.core.os.ConfigurationCompat
-import java.util.*
+import java.util.Locale
import javax.inject.Inject
class LocaleProvider @Inject constructor(private val resources: Resources) {
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/DebouncedClickListener.kt b/vector/src/main/java/im/vector/riotx/core/utils/DebouncedClickListener.kt
index 958f642565..230b11f14d 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/DebouncedClickListener.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/DebouncedClickListener.kt
@@ -16,7 +16,7 @@
package im.vector.riotx.core.utils
import android.view.View
-import java.util.*
+import java.util.WeakHashMap
/**
* Simple Debounced OnClickListener
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt
index c65fcafb16..a5babcc885 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt
@@ -59,9 +59,10 @@ fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) {
val jsonAdapter = moshi.adapter(EmojiDataSource.EmojiData::class.java)
val inputAsString = input.bufferedReader().use { it.readText() }
val source = jsonAdapter.fromJson(inputAsString)
- knownEmojiSet = HashSet()
- source?.emojis?.values?.forEach {
- knownEmojiSet?.add(it.emojiString())
+ knownEmojiSet = HashSet().also {
+ source?.emojis?.mapTo(it) { (_, value) ->
+ value.emojiString()
+ }
}
done?.invoke()
}
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt
index 9572b07216..78242d58de 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt
@@ -32,7 +32,8 @@ import im.vector.riotx.R
import timber.log.Timber
import java.io.File
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
/**
* Open a url in the internet browser of the system
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt
index 8f97ef0247..f8cdeb3de6 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt
@@ -67,6 +67,7 @@ const val PERMISSION_REQUEST_CODE_EXPORT_KEYS = 573
const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
+const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
/**
* Log the used permissions statuses.
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
index 12371fe72d..ba0b99762b 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
@@ -31,7 +31,7 @@ import im.vector.riotx.R
import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.settings.VectorLocale
import timber.log.Timber
-import java.util.*
+import java.util.Locale
/**
* Tells if the application ignores battery optimizations.
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt
index 0b5df0d2e0..75f6893c7c 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt
@@ -19,7 +19,7 @@ package im.vector.riotx.core.utils
import android.content.Context
import android.os.Build
import android.text.format.Formatter
-import java.util.*
+import java.util.TreeMap
object TextUtils {
diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
index ac79ed8b40..9cf6510fdb 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
@@ -44,12 +44,11 @@ object CommandParser {
return ParsedCommand.ErrorNotACommand
}
- var messageParts: List? = null
-
- try {
- messageParts = textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
+ val messageParts = try {
+ textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
} catch (e: Exception) {
Timber.e(e, "## manageSplashCommand() : split failed")
+ null
}
// test if the string cut fails
@@ -57,10 +56,8 @@ object CommandParser {
return ParsedCommand.ErrorEmptySlashCommand
}
- val slashCommand = messageParts[0]
-
- when (slashCommand) {
- Command.CHANGE_DISPLAY_NAME.command -> {
+ when (val slashCommand = messageParts.first()) {
+ Command.CHANGE_DISPLAY_NAME.command -> {
val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()
return if (newDisplayName.isNotEmpty()) {
@@ -69,7 +66,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME)
}
}
- Command.TOPIC.command -> {
+ Command.TOPIC.command -> {
val newTopic = textMessage.substring(Command.TOPIC.command.length).trim()
return if (newTopic.isNotEmpty()) {
@@ -78,12 +75,12 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.TOPIC)
}
}
- Command.EMOTE.command -> {
+ Command.EMOTE.command -> {
val message = textMessage.substring(Command.EMOTE.command.length).trim()
return ParsedCommand.SendEmote(message)
}
- Command.JOIN_ROOM.command -> {
+ Command.JOIN_ROOM.command -> {
val roomAlias = textMessage.substring(Command.JOIN_ROOM.command.length).trim()
return if (roomAlias.isNotEmpty()) {
@@ -92,7 +89,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
}
}
- Command.PART.command -> {
+ Command.PART.command -> {
val roomAlias = textMessage.substring(Command.PART.command.length).trim()
return if (roomAlias.isNotEmpty()) {
@@ -101,7 +98,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.PART)
}
}
- Command.INVITE.command -> {
+ Command.INVITE.command -> {
return if (messageParts.size == 2) {
val userId = messageParts[1]
@@ -114,7 +111,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.INVITE)
}
}
- Command.KICK_USER.command -> {
+ Command.KICK_USER.command -> {
return if (messageParts.size >= 2) {
val userId = messageParts[1]
if (MatrixPatterns.isUserId(userId)) {
@@ -130,7 +127,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.KICK_USER)
}
}
- Command.BAN_USER.command -> {
+ Command.BAN_USER.command -> {
return if (messageParts.size >= 2) {
val userId = messageParts[1]
if (MatrixPatterns.isUserId(userId)) {
@@ -146,7 +143,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.BAN_USER)
}
}
- Command.UNBAN_USER.command -> {
+ Command.UNBAN_USER.command -> {
return if (messageParts.size == 2) {
val userId = messageParts[1]
@@ -159,7 +156,7 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
}
}
- Command.SET_USER_POWER_LEVEL.command -> {
+ Command.SET_USER_POWER_LEVEL.command -> {
return if (messageParts.size == 3) {
val userId = messageParts[1]
if (MatrixPatterns.isUserId(userId)) {
@@ -192,25 +189,25 @@ object CommandParser {
ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
}
}
- Command.MARKDOWN.command -> {
+ Command.MARKDOWN.command -> {
return if (messageParts.size == 2) {
when {
- "on".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(true)
+ "on".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(true)
"off".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(false)
- else -> ParsedCommand.ErrorSyntax(Command.MARKDOWN)
+ else -> ParsedCommand.ErrorSyntax(Command.MARKDOWN)
}
} else {
ParsedCommand.ErrorSyntax(Command.MARKDOWN)
}
}
- Command.CLEAR_SCALAR_TOKEN.command -> {
+ Command.CLEAR_SCALAR_TOKEN.command -> {
return if (messageParts.size == 1) {
ParsedCommand.ClearScalarToken
} else {
ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
}
}
- else -> {
+ else -> {
// Unknown command
return ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
}
diff --git a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
index ec8f1c7fa2..adf8421842 100644
--- a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
+++ b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
@@ -24,7 +24,7 @@ import im.vector.riotx.features.settings.FontScale
import im.vector.riotx.features.settings.VectorLocale
import im.vector.riotx.features.themes.ThemeUtils
import timber.log.Timber
-import java.util.*
+import java.util.Locale
import javax.inject.Inject
/**
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysExporter.kt
index 54e3a34744..9642c2d8c6 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysExporter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysExporter.kt
@@ -18,10 +18,10 @@ package im.vector.riotx.features.crypto.keys
import android.content.Context
import android.os.Environment
-import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.extensions.foldToCallback
+import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.riotx.core.files.addEntryToDownloadManager
import im.vector.riotx.core.files.writeToFile
import kotlinx.coroutines.Dispatchers
@@ -36,28 +36,20 @@ class KeysExporter(private val session: Session) {
* Export keys and return the file path with the callback
*/
fun export(context: Context, password: String, callback: MatrixCallback) {
- session.exportRoomKeys(password, object : MatrixCallback {
- override fun onSuccess(data: ByteArray) {
- GlobalScope.launch(Dispatchers.Main) {
- withContext(Dispatchers.IO) {
- Try {
- val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
- val file = File(parentDir, "riotx-keys-" + System.currentTimeMillis() + ".txt")
+ GlobalScope.launch(Dispatchers.Main) {
+ runCatching {
+ val data = awaitCallback { session.exportRoomKeys(password, it) }
+ withContext(Dispatchers.IO) {
+ val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ val file = File(parentDir, "riotx-keys-" + System.currentTimeMillis() + ".txt")
- writeToFile(data, file)
+ writeToFile(data, file)
- addEntryToDownloadManager(context, file, "text/plain")
+ addEntryToDownloadManager(context, file, "text/plain")
- file.absolutePath
- }
- }
- .foldToCallback(callback)
+ file.absolutePath
}
- }
-
- override fun onFailure(failure: Throwable) {
- callback.onFailure(failure)
- }
- })
+ }.foldToCallback(callback)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysImporter.kt
index 74b2a86bc1..b60e25af04 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysImporter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysImporter.kt
@@ -18,10 +18,11 @@ package im.vector.riotx.features.crypto.keys
import android.content.Context
import android.net.Uri
-import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
+import im.vector.matrix.android.internal.extensions.foldToCallback
+import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.riotx.core.intent.getMimeTypeFromUri
import im.vector.riotx.core.resources.openResource
import kotlinx.coroutines.Dispatchers
@@ -41,8 +42,8 @@ class KeysImporter(private val session: Session) {
password: String,
callback: MatrixCallback) {
GlobalScope.launch(Dispatchers.Main) {
- withContext(Dispatchers.IO) {
- Try {
+ runCatching {
+ withContext(Dispatchers.IO) {
val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri))
if (resource?.mContentStream == null) {
@@ -51,33 +52,17 @@ class KeysImporter(private val session: Session) {
val data: ByteArray
try {
- data = ByteArray(resource.mContentStream!!.available())
- resource.mContentStream!!.read(data)
- resource.mContentStream!!.close()
-
- data
+ data = resource.mContentStream!!.use { it.readBytes() }
} catch (e: Exception) {
- try {
- resource.mContentStream!!.close()
- } catch (e2: Exception) {
- Timber.e(e2, "## importKeys()")
- }
-
+ Timber.e(e, "## importKeys()")
throw e
}
+
+ awaitCallback {
+ session.importRoomKeys(data, password, null, it)
+ }
}
- }
- .fold(
- {
- callback.onFailure(it)
- },
- { byteArray ->
- session.importRoomKeys(byteArray,
- password,
- null,
- callback)
- }
- )
+ }.foldToCallback(callback)
}
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
index 6b01a7dffa..7b60cb2f9b 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
@@ -31,7 +31,7 @@ import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.GenericItem
import im.vector.riotx.core.ui.list.genericItem
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
class KeysBackupSettingsRecyclerViewController @Inject constructor(private val stringProvider: StringProvider,
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
index 7b61ca2c0f..a5cc0510da 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
@@ -170,8 +170,8 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
private fun exportRecoveryKeyToFile(data: String) {
GlobalScope.launch(Dispatchers.Main) {
- withContext(Dispatchers.IO) {
- Try {
+ Try {
+ withContext(Dispatchers.IO) {
val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(parentDir, "recovery-key-" + System.currentTimeMillis() + ".txt")
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index a525e7acc6..4875c87ec0 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -81,8 +81,6 @@ import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
import im.vector.riotx.core.ui.views.NotificationAreaView
import im.vector.riotx.core.utils.*
-import im.vector.riotx.core.utils.Debouncer
-import im.vector.riotx.core.utils.createUIHandler
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
import im.vector.riotx.features.attachments.AttachmentsHelper
import im.vector.riotx.features.attachments.ContactAttachment
@@ -412,7 +410,7 @@ class RoomDetailFragment :
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
- avatarRenderer.render(event.senderAvatar, event.root.senderId ?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
+ avatarRenderer.render(event.senderAvatar, event.root.senderId ?: "", event.getDisambiguatedDisplayName(), composerLayout.composerRelatedMessageAvatar)
composerLayout.expand {
// need to do it here also when not using quick reply
focusComposerAndShowKeyboard()
@@ -483,7 +481,7 @@ class RoomDetailFragment :
jumpToReadMarkerView.render(show, readMarkerId)
}
}
- recyclerView.setController(timelineEventController)
+ recyclerView.adapter = timelineEventController.adapter
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
@@ -622,19 +620,27 @@ class RoomDetailFragment :
}
composerLayout.callback = object : TextComposerView.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean {
- val shareIntent = Intent().apply {
- action = Intent.ACTION_SEND
- data = contentUri
+ // We need WRITE_EXTERNAL permission
+ return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this@RoomDetailFragment, PERMISSION_REQUEST_CODE_INCOMING_URI)) {
+ sendUri(contentUri)
+ } else {
+ roomDetailViewModel.pendingUri = contentUri
+ // Always intercept when we request some permission
+ true
}
- val isHandled = attachmentsHelper.handleShareIntent(shareIntent)
- if (!isHandled) {
- Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
- }
- return isHandled
}
}
}
+ private fun sendUri(uri: Uri): Boolean {
+ val shareIntent = Intent(Intent.ACTION_SEND, uri)
+ val isHandled = attachmentsHelper.handleShareIntent(shareIntent)
+ if (!isHandled) {
+ Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
+ }
+ return isHandled
+ }
+
private fun setupAttachmentButton() {
composerLayout.attachmentButton.setOnClickListener {
if (!::attachmentTypeSelector.isInitialized) {
@@ -909,19 +915,34 @@ class RoomDetailFragment :
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
if (allGranted(grantResults)) {
- if (requestCode == PERMISSION_REQUEST_CODE_DOWNLOAD_FILE) {
- val action = roomDetailViewModel.pendingAction
- if (action != null) {
- roomDetailViewModel.pendingAction = null
- roomDetailViewModel.process(action)
+ when (requestCode) {
+ PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> {
+ val action = roomDetailViewModel.pendingAction
+ if (action != null) {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.process(action)
+ }
}
- } else if (requestCode == PERMISSION_REQUEST_CODE_PICK_ATTACHMENT) {
- val pendingType = attachmentsHelper.pendingType
- if (pendingType != null) {
- attachmentsHelper.pendingType = null
- launchAttachmentProcess(pendingType)
+ PERMISSION_REQUEST_CODE_INCOMING_URI -> {
+ val pendingUri = roomDetailViewModel.pendingUri
+ if (pendingUri != null) {
+ roomDetailViewModel.pendingUri = null
+ sendUri(pendingUri)
+ }
+ }
+ PERMISSION_REQUEST_CODE_PICK_ATTACHMENT -> {
+ val pendingType = attachmentsHelper.pendingType
+ if (pendingType != null) {
+ attachmentsHelper.pendingType = null
+ launchAttachmentProcess(pendingType)
+ }
}
}
+ } else {
+ // Reset all pending data
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.pendingUri = null
+ attachmentsHelper.pendingType = null
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index f3934f618c..b1c6aa02fb 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -16,6 +16,7 @@
package im.vector.riotx.features.home.room.detail
+import android.net.Uri
import androidx.annotation.IdRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -95,6 +96,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
// Slot to keep a pending action during permission request
var pendingAction: RoomDetailActions? = null
+ // Slot to keep a pending uri during permission request
+ var pendingUri: Uri? = null
@AssistedInject.Factory
interface Factory {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
index 84917d682b..69ecc30583 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
@@ -76,11 +76,8 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
Observable.combineLatest, Option, List>(
room.rx().liveRoomMemberIds(),
usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
- BiFunction { roomMembers, query ->
- val users = roomMembers
- .mapNotNull {
- session.getUser(it)
- }
+ BiFunction { roomMemberIds, query ->
+ val users = roomMemberIds.mapNotNull { session.getUser(it) }
val filter = query.orNull()
if (filter.isNullOrBlank()) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 135496264d..63a4919763 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -42,7 +42,8 @@ import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventForm
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.html.EventHtmlRenderer
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
/**
* Quick reactions state
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
index d36e98f67c..4661d8f8cd 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
@@ -38,7 +38,7 @@ import im.vector.riotx.features.html.EventHtmlRenderer
import me.gujun.android.span.span
import name.fraser.neil.plaintext.diff_match_patch
import timber.log.Timber
-import java.util.*
+import java.util.Calendar
/**
* Epoxy controller for edit history list
@@ -94,7 +94,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
val body = cContent.second?.let { eventHtmlRenderer.render(it) }
?: cContent.first
- val nextEvent = if (index + 1 <= sourceEvents.lastIndex) sourceEvents[index + 1] else null
+ val nextEvent = sourceEvents.getOrNull(index + 1)
var spannedDiff: Spannable? = null
if (nextEvent != null && cContent.second == null /*No diff for html*/) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index e2b976b273..93e7709b55 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -30,7 +30,7 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
import timber.log.Timber
-import java.util.*
+import java.util.UUID
data class ViewEditHistoryViewState(
val eventId: String,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
index 12f49c2e74..3f234fcd3e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
@@ -27,8 +27,6 @@ import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
-import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
-import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
@@ -41,13 +39,13 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
fun create(event: TimelineEvent,
highlight: Boolean,
callback: TimelineEventController.Callback?): NoticeItem? {
- val text = buildNoticeText(event.root, event.senderName) ?: return null
+ val text = buildNoticeText(event.root, event.getDisambiguatedDisplayName()) ?: return null
val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
- avatarUrl = event.senderAvatar(),
- memberName = event.senderName(),
+ avatarUrl = event.senderAvatar,
+ memberName = event.getDisambiguatedDisplayName(),
showInformation = false
)
val attributes = NoticeItem.Attributes(
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 1df885cd35..51364e24c9 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -64,12 +64,12 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
if (!showReadMarker && mergedEvent.hasReadMarker && readMarkerVisible) {
showReadMarker = true
}
- val senderAvatar = mergedEvent.senderAvatar()
- val senderName = mergedEvent.senderName()
+ val senderAvatar = mergedEvent.senderAvatar
+ val senderName = mergedEvent.getDisambiguatedDisplayName()
val data = MergedHeaderItem.Data(
userId = mergedEvent.root.senderId ?: "",
avatarUrl = senderAvatar,
- memberName = senderName ?: "",
+ memberName = senderName,
localId = mergedEvent.localId,
eventId = mergedEvent.root.eventId ?: ""
)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 0bb5c3a1d8..eacd702b7d 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -46,9 +46,11 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
import im.vector.riotx.features.home.room.detail.timeline.helper.*
import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.html.EventHtmlRenderer
+import im.vector.riotx.features.html.CodeVisitor
import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer
import me.gujun.android.span.span
+import org.commonmark.node.Document
import javax.inject.Inject
class MessageItemFactory @Inject constructor(
@@ -97,16 +99,8 @@ class MessageItemFactory @Inject constructor(
// val all = event.root.toContent()
// val ev = all.toModel()
return when (messageContent) {
- is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
- informationData,
- highlight,
- callback,
- attributes)
- is MessageTextContent -> buildTextMessageItem(messageContent,
- informationData,
- highlight,
- callback,
- attributes)
+ is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
+ is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
@@ -229,34 +223,75 @@ class MessageItemFactory @Inject constructor(
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
}
- private fun buildTextMessageItem(messageContent: MessageTextContent,
+ private fun buildItemForTextContent(messageContent: MessageTextContent,
+ informationData: MessageInformationData,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
+ val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
+ return if (isFormatted) {
+ val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
+ val codeVisitor = CodeVisitor()
+ codeVisitor.visit(localFormattedBody)
+ when (codeVisitor.codeKind) {
+ CodeVisitor.Kind.BLOCK -> {
+ val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody)
+ buildCodeBlockItem(codeFormattedBlock, informationData, highlight, callback, attributes)
+ }
+ CodeVisitor.Kind.INLINE -> {
+ val codeFormatted = htmlRenderer.get().render(localFormattedBody)
+ buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
+ }
+ CodeVisitor.Kind.NONE -> {
+ val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!)
+ buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
+ }
+ }
+ } else {
+ buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
+ }
+ }
+
+ private fun buildMessageTextItem(body: CharSequence,
+ isFormatted: Boolean,
informationData: MessageInformationData,
highlight: Boolean,
callback: TimelineEventController.Callback?,
attributes: AbsMessageItem.Attributes): MessageTextItem? {
- val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
- val bodyToUse = messageContent.formattedBody?.let {
- htmlRenderer.get().render(it.trim())
- } ?: messageContent.body
+ val linkifiedBody = linkifyBody(body, callback)
- val linkifiedBody = linkifyBody(bodyToUse, callback)
-
- return MessageTextItem_()
- .apply {
- if (informationData.hasBeenEdited) {
- val spannable = annotateWithEdited(linkifiedBody, callback, informationData)
- message(spannable)
- } else {
- message(linkifiedBody)
- }
- }
+ return MessageTextItem_().apply {
+ if (informationData.hasBeenEdited) {
+ val spannable = annotateWithEdited(linkifiedBody, callback, informationData)
+ message(spannable)
+ } else {
+ message(linkifiedBody)
+ }
+ }
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
.searchForPills(isFormatted)
.leftGuideline(avatarSizeProvider.leftGuideline)
.attributes(attributes)
.highlighted(highlight)
.urlClickCallback(callback)
- // click on the text
+ }
+
+ private fun buildCodeBlockItem(formattedBody: CharSequence,
+ informationData: MessageInformationData,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ attributes: AbsMessageItem.Attributes): MessageBlockCodeItem? {
+ return MessageBlockCodeItem_()
+ .apply {
+ if (informationData.hasBeenEdited) {
+ val spannable = annotateWithEdited("", callback, informationData)
+ editedSpan(spannable)
+ }
+ }
+ .leftGuideline(avatarSizeProvider.leftGuideline)
+ .attributes(attributes)
+ .highlighted(highlight)
+ .message(formattedBody)
}
private fun annotateWithEdited(linkifiedBody: CharSequence,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
index 2d116e4a90..9a86420277 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
@@ -19,13 +19,17 @@ package im.vector.riotx.features.home.room.detail.timeline.format
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
-import im.vector.matrix.android.api.session.room.model.*
+import im.vector.matrix.android.api.session.room.model.Membership
+import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
+import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
+import im.vector.matrix.android.api.session.room.model.RoomMember
+import im.vector.matrix.android.api.session.room.model.RoomNameContent
+import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.resources.StringProvider
-import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import timber.log.Timber
import javax.inject.Inject
@@ -36,7 +40,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
return when (val type = timelineEvent.root.getClearType()) {
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
- EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderName())
+ EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName())
EventType.CALL_INVITE,
@@ -96,7 +100,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
- val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null
+ val historyVisibility = event.getClearContent().toModel()?.historyVisibility
+ ?: return null
val formattedVisibility = when (historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
@@ -135,7 +140,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}
}
- private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
+ private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String {
val displayText = StringBuilder()
// Check display name has been changed
if (eventContent?.displayName != prevEventContent?.displayName) {
@@ -146,7 +151,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
else ->
stringProvider.getString(R.string.notice_display_name_changed_from,
- event.senderId, prevEventContent?.displayName, eventContent?.displayName)
+ event.senderId, prevEventContent?.displayName, eventContent?.displayName)
}
displayText.append(displayNameText)
}
@@ -160,6 +165,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}
displayText.append(displayAvatarText)
}
+ if (displayText.isEmpty()) {
+ displayText.append(
+ stringProvider.getString(R.string.notice_member_no_changes, senderName)
+ )
+ }
return displayText.toString()
}
@@ -171,9 +181,10 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId
when {
eventContent.thirdPartyInvite != null -> {
- val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey
+ val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid
+ ?: event.stateKey
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
- userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName)
+ userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName)
}
event.stateKey == selfUserId ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
index a75ac86c1b..f40b8e6f6d 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
@@ -17,8 +17,6 @@
package im.vector.riotx.features.home.room.detail.timeline.helper
import im.vector.matrix.android.api.session.events.model.EventType
-import im.vector.matrix.android.api.session.events.model.toModel
-import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.core.extensions.localDateTime
@@ -47,25 +45,6 @@ object TimelineDisplayableEvents {
)
}
-fun TimelineEvent.senderAvatar(): String? {
- // We might have no avatar when user leave, so we try to get it from prevContent
- return senderAvatar
- ?: if (root.type == EventType.STATE_ROOM_MEMBER) {
- root.prevContent.toModel()?.avatarUrl
- } else {
- null
- }
-}
-
-fun TimelineEvent.senderName(): String? {
- // We might have no senderName when user leave, so we try to get it from prevContent
- return when {
- senderName != null -> getDisambiguatedDisplayName()
- root.type == EventType.STATE_ROOM_MEMBER -> root.prevContent.toModel()?.displayName
- else -> null
- }
-}
-
fun TimelineEvent.canBeMerged(): Boolean {
return root.getClearType() == EventType.STATE_ROOM_MEMBER
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
new file mode 100644
index 0000000000..82a6a4db6f
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.home.room.detail.timeline.item
+
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.setTextOrHide
+import me.saket.bettermovementmethod.BetterLinkMovementMethod
+
+@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
+abstract class MessageBlockCodeItem : AbsMessageItem() {
+
+ @EpoxyAttribute
+ var message: CharSequence? = null
+ @EpoxyAttribute
+ var editedSpan: CharSequence? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.messageView.text = message
+ renderSendState(holder.messageView, holder.messageView)
+ holder.messageView.setOnClickListener(attributes.itemClickListener)
+ holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
+ holder.editedView.movementMethod = BetterLinkMovementMethod.getInstance()
+ holder.editedView.setTextOrHide(editedSpan)
+ }
+
+ override fun getViewType() = STUB_ID
+
+ class Holder : AbsMessageItem.Holder(STUB_ID) {
+ val messageView by bind(R.id.codeBlockTextView)
+ val editedView by bind(R.id.codeBlockEditedView)
+ }
+
+ companion object {
+ private const val STUB_ID = R.id.messageContentCodeBlockStub
+ }
+}
diff --git a/vector/src/test/java/im/vector/riotx/ExampleUnitTest.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt
similarity index 64%
rename from vector/src/test/java/im/vector/riotx/ExampleUnitTest.kt
rename to vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt
index c51f642a1b..4541b5d2b5 100644
--- a/vector/src/test/java/im/vector/riotx/ExampleUnitTest.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt
@@ -14,20 +14,14 @@
* limitations under the License.
*/
-package im.vector.riotx
+package im.vector.riotx.features.home.room.list
-import org.junit.Test
+import dagger.Binds
+import dagger.Module
-import org.junit.Assert.*
+@Module
+abstract class RoomListModule {
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
+ @Binds
+ abstract fun providesRoomListViewModelFactory(roomListViewModelFactory: RoomListViewModelFactory): RoomListViewModel.Factory
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt
index 32061b9cbf..31fa9d0309 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt
@@ -21,8 +21,6 @@ import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.Membership
@@ -31,18 +29,18 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent
-import im.vector.riotx.features.home.HomeRoomListObservableStore
+import im.vector.riotx.core.utils.RxStore
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
+import javax.inject.Inject
-class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
- private val session: Session,
- private val homeRoomListObservableSource: HomeRoomListObservableStore,
- private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
- private val chronologicalRoomComparator: ChronologicalRoomComparator)
+class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
+ private val session: Session,
+ private val roomSummariesStore: RxStore>,
+ private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
+ private val chronologicalRoomComparator: ChronologicalRoomComparator)
: VectorViewModel(initialState) {
- @AssistedInject.Factory
interface Factory {
fun create(initialState: RoomListViewState): RoomListViewModel
}
@@ -103,7 +101,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
}
private fun observeRoomSummaries() {
- homeRoomListObservableSource
+ roomSummariesStore
.observe()
.observeOn(Schedulers.computation())
.map {
@@ -113,7 +111,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
copy(asyncRooms = asyncRooms)
}
- homeRoomListObservableSource
+ roomSummariesStore
.observe()
.observeOn(Schedulers.computation())
.map { buildRoomSummaries(it) }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModelFactory.kt
new file mode 100644
index 0000000000..5895aa4e52
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModelFactory.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.home.room.list
+
+import im.vector.matrix.android.api.session.Session
+import im.vector.riotx.features.home.HomeRoomListObservableStore
+import im.vector.riotx.features.share.ShareRoomListObservableStore
+import javax.inject.Inject
+import javax.inject.Provider
+
+class RoomListViewModelFactory @Inject constructor(private val session: Provider,
+ private val homeRoomListObservableStore: Provider,
+ private val shareRoomListObservableStore: Provider,
+ private val alphabeticalRoomComparator: Provider,
+ private val chronologicalRoomComparator: Provider) : RoomListViewModel.Factory {
+
+ override fun create(initialState: RoomListViewState): RoomListViewModel {
+ return RoomListViewModel(
+ initialState,
+ session.get(),
+ if (initialState.displayMode == RoomListFragment.DisplayMode.SHARE) shareRoomListObservableStore.get() else homeRoomListObservableStore.get(),
+ alphabeticalRoomComparator.get(),
+ chronologicalRoomComparator.get())
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
index 9f73d24c6d..3401e041b1 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
@@ -20,6 +20,8 @@ import androidx.annotation.StringRes
import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.riotx.features.home.room.filtered.filteredRoomFooterItem
@@ -47,24 +49,28 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
override fun buildModels() {
val nonNullViewState = viewState ?: return
- if (nonNullViewState.displayMode == RoomListFragment.DisplayMode.FILTERED) {
- buildFilteredRooms(nonNullViewState)
- } else {
- val roomSummaries = nonNullViewState.asyncFilteredRooms()
- roomSummaries?.forEach { (category, summaries) ->
- if (summaries.isEmpty()) {
- return@forEach
- } else {
- val isExpanded = nonNullViewState.isCategoryExpanded(category)
- buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) {
- listener?.onToggleRoomCategory(category)
- }
- if (isExpanded) {
- buildRoomModels(summaries,
- nonNullViewState.joiningRoomsIds,
- nonNullViewState.joiningErrorRoomsIds,
- nonNullViewState.rejectingRoomsIds,
- nonNullViewState.rejectingErrorRoomsIds)
+ when (nonNullViewState.displayMode) {
+ RoomListFragment.DisplayMode.FILTERED,
+ RoomListFragment.DisplayMode.SHARE -> {
+ buildFilteredRooms(nonNullViewState)
+ }
+ else -> {
+ val roomSummaries = nonNullViewState.asyncFilteredRooms()
+ roomSummaries?.forEach { (category, summaries) ->
+ if (summaries.isEmpty()) {
+ return@forEach
+ } else {
+ val isExpanded = nonNullViewState.isCategoryExpanded(category)
+ buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) {
+ listener?.onToggleRoomCategory(category)
+ }
+ if (isExpanded) {
+ buildRoomModels(summaries,
+ nonNullViewState.joiningRoomsIds,
+ nonNullViewState.joiningErrorRoomsIds,
+ nonNullViewState.rejectingRoomsIds,
+ nonNullViewState.rejectingErrorRoomsIds)
+ }
}
}
}
@@ -80,12 +86,15 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
buildRoomModels(filteredSummaries,
- viewState.joiningRoomsIds,
- viewState.joiningErrorRoomsIds,
- viewState.rejectingRoomsIds,
- viewState.rejectingErrorRoomsIds)
+ viewState.joiningRoomsIds,
+ viewState.joiningErrorRoomsIds,
+ viewState.rejectingRoomsIds,
+ viewState.rejectingErrorRoomsIds)
- addFilterFooter(viewState)
+ when {
+ viewState.displayMode == RoomListFragment.DisplayMode.FILTERED -> addFilterFooter(viewState)
+ filteredSummaries.isEmpty() -> addEmptyFooter()
+ }
}
private fun addFilterFooter(viewState: RoomListViewState) {
@@ -96,6 +105,13 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
}
}
+ private fun addEmptyFooter() {
+ noResultItem {
+ id("no_result")
+ text(stringProvider.getString(R.string.no_result_placeholder))
+ }
+ }
+
private fun buildRoomCategory(viewState: RoomListViewState,
summaries: List,
@StringRes titleRes: Int,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt
index c38c5cfd37..f977fe80fd 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt
@@ -32,7 +32,6 @@ import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
-import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import me.gujun.android.span.span
import javax.inject.Inject
@@ -99,10 +98,10 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
&& latestEvent.root.mxDecryptionResult == null) {
stringProvider.getString(R.string.encrypted_message)
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
- val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
+ val senderName = latestEvent.getDisambiguatedDisplayName()
val content = latestEvent.root.getClearContent()?.toModel()
val message = content?.body ?: ""
- if (roomSummary.isDirect.not() && senderName != null) {
+ if (roomSummary.isDirect.not()) {
span {
text = senderName
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)
diff --git a/vector/src/main/java/im/vector/riotx/features/html/CodeVisitor.kt b/vector/src/main/java/im/vector/riotx/features/html/CodeVisitor.kt
new file mode 100644
index 0000000000..ed8db94fc3
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/html/CodeVisitor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.html
+
+import org.commonmark.node.AbstractVisitor
+import org.commonmark.node.Code
+import org.commonmark.node.FencedCodeBlock
+import org.commonmark.node.IndentedCodeBlock
+
+/**
+ * This class is in charge of visiting nodes and tells if we have some code nodes (inline or block).
+ */
+class CodeVisitor : AbstractVisitor() {
+
+ var codeKind: Kind = Kind.NONE
+ private set
+
+ override fun visit(fencedCodeBlock: FencedCodeBlock?) {
+ if (codeKind == Kind.NONE) {
+ codeKind = Kind.BLOCK
+ }
+ }
+
+ override fun visit(indentedCodeBlock: IndentedCodeBlock?) {
+ if (codeKind == Kind.NONE) {
+ codeKind = Kind.BLOCK
+ }
+ }
+
+ override fun visit(code: Code?) {
+ if (codeKind == Kind.NONE) {
+ codeKind = Kind.INLINE
+ }
+ }
+
+ enum class Kind {
+ NONE,
+ INLINE,
+ BLOCK
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
index 06af8ebca5..dc9e21e440 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
@@ -17,171 +17,46 @@
package im.vector.riotx.features.html
import android.content.Context
-import android.text.style.URLSpan
-import im.vector.matrix.android.api.permalinks.PermalinkData
-import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideApp
-import im.vector.riotx.core.glide.GlideRequests
import im.vector.riotx.features.home.AvatarRenderer
-import org.commonmark.node.BlockQuote
-import org.commonmark.node.HtmlBlock
-import org.commonmark.node.HtmlInline
+import io.noties.markwon.Markwon
+import io.noties.markwon.html.HtmlPlugin
+import io.noties.markwon.html.TagHandlerNoOp
import org.commonmark.node.Node
-import ru.noties.markwon.*
-import ru.noties.markwon.html.HtmlTag
-import ru.noties.markwon.html.MarkwonHtmlParserImpl
-import ru.noties.markwon.html.MarkwonHtmlRenderer
-import ru.noties.markwon.html.TagHandler
-import ru.noties.markwon.html.tag.*
-import java.util.Arrays.asList
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class EventHtmlRenderer @Inject constructor(context: Context,
- avatarRenderer: AvatarRenderer,
- sessionHolder: ActiveSessionHolder) {
+ htmlConfigure: MatrixHtmlPluginConfigure) {
+
private val markwon = Markwon.builder(context)
- .usePlugin(MatrixPlugin.create(GlideApp.with(context), context, avatarRenderer, sessionHolder))
+ .usePlugin(HtmlPlugin.create(htmlConfigure))
.build()
+ fun parse(text: String): Node {
+ return markwon.parse(text)
+ }
+
fun render(text: String): CharSequence {
return markwon.toMarkdown(text)
}
- fun render(node: Node) : CharSequence {
+ fun render(node: Node): CharSequence {
return markwon.render(node)
}
}
-private class MatrixPlugin private constructor(private val glideRequests: GlideRequests,
- private val context: Context,
- private val avatarRenderer: AvatarRenderer,
- private val session: ActiveSessionHolder) : AbstractMarkwonPlugin() {
+class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context,
+ private val avatarRenderer: AvatarRenderer,
+ private val session: ActiveSessionHolder) : HtmlPlugin.HtmlConfigure {
- override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
- builder.htmlParser(MarkwonHtmlParserImpl.create())
- }
-
- override fun configureHtmlRenderer(builder: MarkwonHtmlRenderer.Builder) {
- builder
- .setHandler(
- "img",
- ImageHandler.create())
- .setHandler(
- "a",
- MxLinkHandler(glideRequests, context, avatarRenderer, session))
- .setHandler(
- "blockquote",
- BlockquoteHandler())
- .setHandler(
- "font",
- FontTagHandler())
- .setHandler(
- "sub",
- SubScriptHandler())
- .setHandler(
- "sup",
- SuperScriptHandler())
- .setHandler(
- asList("b", "strong"),
- StrongEmphasisHandler())
- .setHandler(
- asList("s", "del"),
- StrikeHandler())
- .setHandler(
- asList("u", "ins"),
- UnderlineHandler())
- .setHandler(
- asList("ul", "ol"),
- ListHandler())
- .setHandler(
- asList("i", "em", "cite", "dfn"),
- EmphasisHandler())
- .setHandler(
- asList("h1", "h2", "h3", "h4", "h5", "h6"),
- HeadingHandler())
- .setHandler("mx-reply",
- MxReplyTagHandler())
- }
-
- override fun afterRender(node: Node, visitor: MarkwonVisitor) {
- val configuration = visitor.configuration()
- configuration.htmlRenderer().render(visitor, configuration.htmlParser())
- }
-
- override fun configureVisitor(builder: MarkwonVisitor.Builder) {
- builder
- .on(HtmlBlock::class.java) { visitor, htmlBlock -> visitHtml(visitor, htmlBlock.literal) }
- .on(HtmlInline::class.java) { visitor, htmlInline -> visitHtml(visitor, htmlInline.literal) }
- }
-
- private fun visitHtml(visitor: MarkwonVisitor, html: String?) {
- if (html != null) {
- visitor.configuration().htmlParser().processFragment(visitor.builder(), html)
- }
- }
-
- companion object {
-
- fun create(glideRequests: GlideRequests, context: Context, avatarRenderer: AvatarRenderer, session: ActiveSessionHolder): MatrixPlugin {
- return MatrixPlugin(glideRequests, context, avatarRenderer, session)
- }
- }
-}
-
-private class MxLinkHandler(private val glideRequests: GlideRequests,
- private val context: Context,
- private val avatarRenderer: AvatarRenderer,
- private val sessionHolder: ActiveSessionHolder) : TagHandler() {
-
- private val linkHandler = LinkHandler()
-
- override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
- val link = tag.attributes()["href"]
- if (link != null) {
- val permalinkData = PermalinkParser.parse(link)
- when (permalinkData) {
- is PermalinkData.UserLink -> {
- val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
- val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user)
- SpannableBuilder.setSpans(
- visitor.builder(),
- span,
- tag.start(),
- tag.end()
- )
- // also add clickable span
- SpannableBuilder.setSpans(
- visitor.builder(),
- URLSpan(link),
- tag.start(),
- tag.end()
- )
- }
- else -> linkHandler.handle(visitor, renderer, tag)
- }
- } else {
- linkHandler.handle(visitor, renderer, tag)
- }
- }
-}
-
-private class MxReplyTagHandler : TagHandler() {
-
- override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
- val configuration = visitor.configuration()
- val factory = configuration.spansFactory().get(BlockQuote::class.java)
- if (factory != null) {
- SpannableBuilder.setSpans(
- visitor.builder(),
- factory.getSpans(configuration, visitor.renderProps()),
- tag.start(),
- tag.end()
- )
- val replyText = visitor.builder().removeFromEnd(tag.end())
- visitor.builder().append("\n\n").append(replyText)
- }
+ override fun configureHtml(plugin: HtmlPlugin) {
+ plugin
+ .addHandler(TagHandlerNoOp.create("a"))
+ .addHandler(FontTagHandler())
+ .addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session))
+ .addHandler(MxReplyTagHandler())
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/html/FontTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/FontTagHandler.kt
index f4fa1737c9..e5733dd849 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/FontTagHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/FontTagHandler.kt
@@ -17,15 +17,18 @@ package im.vector.riotx.features.html
import android.graphics.Color
import android.text.style.ForegroundColorSpan
-import ru.noties.markwon.MarkwonConfiguration
-import ru.noties.markwon.RenderProps
-import ru.noties.markwon.html.HtmlTag
-import ru.noties.markwon.html.tag.SimpleTagHandler
+import io.noties.markwon.MarkwonConfiguration
+import io.noties.markwon.RenderProps
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.tag.SimpleTagHandler
/**
* custom to matrix for IRC-style font coloring
*/
class FontTagHandler : SimpleTagHandler() {
+
+ override fun supportedTags() = listOf("font")
+
override fun getSpans(configuration: MarkwonConfiguration, renderProps: RenderProps, tag: HtmlTag): Any? {
val colorString = tag.attributes()["color"]?.let { parseColor(it) } ?: Color.BLACK
return ForegroundColorSpan(colorString)
@@ -37,23 +40,23 @@ class FontTagHandler : SimpleTagHandler() {
} catch (e: Exception) {
// try other w3c colors?
return when (color_name) {
- "white" -> Color.WHITE
- "yellow" -> Color.YELLOW
+ "white" -> Color.WHITE
+ "yellow" -> Color.YELLOW
"fuchsia" -> Color.parseColor("#FF00FF")
- "red" -> Color.RED
- "silver" -> Color.parseColor("#C0C0C0")
- "gray" -> Color.GRAY
- "olive" -> Color.parseColor("#808000")
- "purple" -> Color.parseColor("#800080")
- "maroon" -> Color.parseColor("#800000")
- "aqua" -> Color.parseColor("#00FFFF")
- "lime" -> Color.parseColor("#00FF00")
- "teal" -> Color.parseColor("#008080")
- "green" -> Color.GREEN
- "blue" -> Color.BLUE
- "orange" -> Color.parseColor("#FFA500")
- "navy" -> Color.parseColor("#000080")
- else -> Color.BLACK
+ "red" -> Color.RED
+ "silver" -> Color.parseColor("#C0C0C0")
+ "gray" -> Color.GRAY
+ "olive" -> Color.parseColor("#808000")
+ "purple" -> Color.parseColor("#800080")
+ "maroon" -> Color.parseColor("#800000")
+ "aqua" -> Color.parseColor("#00FFFF")
+ "lime" -> Color.parseColor("#00FF00")
+ "teal" -> Color.parseColor("#008080")
+ "green" -> Color.GREEN
+ "blue" -> Color.BLUE
+ "orange" -> Color.parseColor("#FFA500")
+ "navy" -> Color.parseColor("#000080")
+ else -> Color.BLACK
}
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt
new file mode 100644
index 0000000000..fdcbb12cd7
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.html
+
+import android.content.Context
+import android.text.style.URLSpan
+import im.vector.matrix.android.api.permalinks.PermalinkData
+import im.vector.matrix.android.api.permalinks.PermalinkParser
+import im.vector.riotx.core.di.ActiveSessionHolder
+import im.vector.riotx.core.glide.GlideRequests
+import im.vector.riotx.features.home.AvatarRenderer
+import io.noties.markwon.MarkwonVisitor
+import io.noties.markwon.SpannableBuilder
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.MarkwonHtmlRenderer
+import io.noties.markwon.html.tag.LinkHandler
+
+class MxLinkTagHandler(private val glideRequests: GlideRequests,
+ private val context: Context,
+ private val avatarRenderer: AvatarRenderer,
+ private val sessionHolder: ActiveSessionHolder) : LinkHandler() {
+
+ override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+ val link = tag.attributes()["href"]
+ if (link != null) {
+ val permalinkData = PermalinkParser.parse(link)
+ when (permalinkData) {
+ is PermalinkData.UserLink -> {
+ val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
+ val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user)
+ SpannableBuilder.setSpans(
+ visitor.builder(),
+ span,
+ tag.start(),
+ tag.end()
+ )
+ // also add clickable span
+ SpannableBuilder.setSpans(
+ visitor.builder(),
+ URLSpan(link),
+ tag.start(),
+ tag.end()
+ )
+ }
+ else -> super.handle(visitor, renderer, tag)
+ }
+ } else {
+ super.handle(visitor, renderer, tag)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxReplyTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxReplyTagHandler.kt
new file mode 100644
index 0000000000..f999e253c7
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/html/MxReplyTagHandler.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.html
+
+import io.noties.markwon.MarkwonVisitor
+import io.noties.markwon.SpannableBuilder
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.MarkwonHtmlRenderer
+import io.noties.markwon.html.TagHandler
+import org.commonmark.node.BlockQuote
+
+class MxReplyTagHandler : TagHandler() {
+
+ override fun supportedTags() = listOf("mx-reply")
+
+ override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+ val configuration = visitor.configuration()
+ val factory = configuration.spansFactory().get(BlockQuote::class.java)
+ if (factory != null) {
+ SpannableBuilder.setSpans(
+ visitor.builder(),
+ factory.getSpans(configuration, visitor.renderProps()),
+ tag.start(),
+ tag.end()
+ )
+ val replyText = visitor.builder().removeFromEnd(tag.end())
+ visitor.builder().append("\n\n").append(replyText)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt
index 06108e07fe..e38e7d548a 100644
--- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt
+++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt
@@ -33,7 +33,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
import timber.log.Timber
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
/**
@@ -94,7 +94,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
event.getLastMessageBody()
?: stringProvider.getString(R.string.notification_unknown_new_event)
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
- val senderDisplayName = event.senderName ?: event.root.senderId
+ val senderDisplayName = event.getDisambiguatedDisplayName()
val notifiableEvent = NotifiableMessageEvent(
eventId = event.root.eventId!!,
@@ -128,7 +128,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
val body = event.getLastMessageBody()
?: stringProvider.getString(R.string.notification_unknown_new_event)
val roomName = room.roomSummary()?.displayName ?: ""
- val senderDisplayName = event.senderName ?: event.root.senderId
+ val senderDisplayName = event.getDisambiguatedDisplayName()
val notifiableEvent = NotifiableMessageEvent(
eventId = event.root.eventId!!,
diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt
index e26395641d..63cd1c5ce6 100644
--- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt
+++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt
@@ -27,7 +27,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.vectorComponent
import timber.log.Timber
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
/**
diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
index 1a5385663b..7d8e43d0be 100755
--- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
@@ -45,9 +45,9 @@ import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.settings.VectorPreferences
import timber.log.Timber
-import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.random.Random
/**
* Util class for creating notifications.
@@ -299,7 +299,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
// use a generator for the private requestCode.
// When using 0, the intent is not created/launched when the user taps on the notification.
//
- val pendingIntent = stackBuilder.getPendingIntent(Random().nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT)
+ val pendingIntent = stackBuilder.getPendingIntent(Random.nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(pendingIntent)
@@ -599,7 +599,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val intent = HomeActivity.newIntent(context, clearNotification = true)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.data = Uri.parse("foobar://tapSummary")
- return PendingIntent.getActivity(context, Random().nextInt(1000), intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getActivity(context, Random.nextInt(1000), intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/*
diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
index 9a7707d063..b96542a8ce 100755
--- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
@@ -46,7 +46,7 @@ import org.json.JSONObject
import timber.log.Timber
import java.io.*
import java.net.HttpURLConnection
-import java.util.*
+import java.util.Locale
import java.util.zip.GZIPOutputStream
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
index 0b9cb5798c..95053790c8 100644
--- a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
+++ b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
@@ -24,7 +24,9 @@ import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
import java.util.logging.*
import java.util.logging.Formatter
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
index 7fef12cddf..93931fe71d 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
@@ -21,12 +21,16 @@ import android.content.res.Configuration
import android.os.Build
import android.preference.PreferenceManager
import androidx.core.content.edit
+import im.vector.riotx.BuildConfig
import im.vector.riotx.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.Locale
+import kotlin.Comparator
+import kotlin.collections.ArrayList
+import kotlin.collections.HashSet
/**
* Object to manage the Locale choice of the user
@@ -35,6 +39,7 @@ object VectorLocale {
private const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY"
private const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY"
private const val APPLICATION_LOCALE_LANGUAGE_KEY = "APPLICATION_LOCALE_LANGUAGE_KEY"
+ private const val APPLICATION_LOCALE_SCRIPT_KEY = "APPLICATION_LOCALE_SCRIPT_KEY"
private val defaultLocale = Locale("en", "US")
@@ -106,6 +111,15 @@ object VectorLocale {
} else {
putString(APPLICATION_LOCALE_VARIANT_KEY, variant)
}
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ val script = locale.script
+ if (script.isEmpty()) {
+ remove(APPLICATION_LOCALE_SCRIPT_KEY)
+ } else {
+ putString(APPLICATION_LOCALE_SCRIPT_KEY, script)
+ }
+ }
}
}
@@ -159,24 +173,43 @@ object VectorLocale {
* @param context the context
*/
private fun initApplicationLocales(context: Context) {
- val knownLocalesSet = HashSet>()
+ val knownLocalesSet = HashSet>()
try {
val availableLocales = Locale.getAvailableLocales()
for (locale in availableLocales) {
- knownLocalesSet.add(Pair(getString(context, locale, R.string.resources_language),
- getString(context, locale, R.string.resources_country_code)))
+ knownLocalesSet.add(
+ Triple(
+ getString(context, locale, R.string.resources_language),
+ getString(context, locale, R.string.resources_country_code),
+ getString(context, locale, R.string.resources_script)
+ )
+ )
}
} catch (e: Exception) {
Timber.e(e, "## getApplicationLocales() : failed")
- knownLocalesSet.add(Pair(context.getString(R.string.resources_language), context.getString(R.string.resources_country_code)))
+ knownLocalesSet.add(
+ Triple(
+ context.getString(R.string.resources_language),
+ context.getString(R.string.resources_country_code),
+ context.getString(R.string.resources_script)
+ )
+ )
}
supportedLocales.clear()
- knownLocalesSet.mapTo(supportedLocales) { (language, country) ->
- Locale(language, country)
+ knownLocalesSet.mapTo(supportedLocales) { (language, country, script) ->
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Locale.Builder()
+ .setLanguage(language)
+ .setRegion(country)
+ .setScript(script)
+ .build()
+ } else {
+ Locale(language, country)
+ }
}
// sort by human display names
@@ -190,12 +223,37 @@ object VectorLocale {
* @return the string
*/
fun localeToLocalisedString(locale: Locale): String {
- var res = locale.getDisplayLanguage(locale)
+ return buildString {
+ append(locale.getDisplayLanguage(locale))
- if (locale.getDisplayCountry(locale).isNotEmpty()) {
- res += " (" + locale.getDisplayCountry(locale) + ")"
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+ && locale.script != "Latn"
+ && locale.getDisplayScript(locale).isNotEmpty()) {
+ append(" - ")
+ append(locale.getDisplayScript(locale))
+ }
+
+ if (locale.getDisplayCountry(locale).isNotEmpty()) {
+ append(" (")
+ append(locale.getDisplayCountry(locale))
+ append(")")
+ }
+
+ // In debug mode, also display information about the locale in the current locale.
+ if (BuildConfig.DEBUG) {
+ append("\n[")
+ append(locale.displayLanguage)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && locale.script != "Latn") {
+ append(" - ")
+ append(locale.displayScript)
+ }
+ if (locale.displayCountry.isNotEmpty()) {
+ append(" (")
+ append(locale.displayCountry)
+ append(")")
+ }
+ append("]")
+ }
}
-
- return res
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
index f9601265d3..a593bb6e96 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
@@ -576,7 +576,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @return true if the markdown is enabled
*/
fun isMarkdownEnabled(): Boolean {
- return defaultPrefs.getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, true)
+ return defaultPrefs.getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, false)
}
/**
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
index 331b6e935a..ff76c61754 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
@@ -51,7 +51,6 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
-import java.util.*
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
index 2f52cdef13..a78529f06c 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
@@ -56,7 +56,8 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActiv
import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt
index 0d2f9ee040..5e471cf78b 100644
--- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt
@@ -20,6 +20,8 @@ import android.content.ClipDescription
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
+import androidx.appcompat.widget.SearchView
+import com.airbnb.mvrx.viewModel
import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R
@@ -39,8 +41,10 @@ class IncomingShareActivity :
VectorBaseActivity(), AttachmentsHelper.Callback {
@Inject lateinit var sessionHolder: ActiveSessionHolder
- private lateinit var roomListFragment: RoomListFragment
+ @Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory
+ private var roomListFragment: RoomListFragment? = null
private lateinit var attachmentsHelper: AttachmentsHelper
+ private val incomingShareViewModel: IncomingShareViewModel by viewModel()
override fun getLayoutRes(): Int {
return R.layout.activity_incoming_share
@@ -77,12 +81,23 @@ class IncomingShareActivity :
} else {
cannotManageShare()
}
+
+ incomingShareSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String): Boolean {
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ roomListFragment?.filterRoomsWith(newText)
+ return true
+ }
+ })
}
override fun onContentAttachmentsReady(attachments: List) {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
roomListFragment = RoomListFragment.newInstance(roomListParams)
- replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer)
+ .also { replaceFragment(it, R.id.shareRoomListFragmentContainer) }
}
override fun onAttachmentsProcessFailed() {
@@ -102,7 +117,7 @@ class IncomingShareActivity :
} else {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
roomListFragment = RoomListFragment.newInstance(roomListParams)
- replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer)
+ .also { replaceFragment(it, R.id.shareRoomListFragmentContainer) }
true
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
new file mode 100644
index 0000000000..51485ecbf9
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.share
+
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.rx.rx
+import im.vector.riotx.ActiveSessionObservableStore
+import im.vector.riotx.core.platform.VectorViewModel
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import java.util.concurrent.TimeUnit
+
+data class IncomingShareState(private val dummy: Boolean = false) : MvRxState
+
+/**
+ * View model used to observe the room list and post update to the ShareRoomListObservableStore
+ */
+class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState,
+ private val sessionObservableStore: ActiveSessionObservableStore,
+ private val shareRoomListObservableStore: ShareRoomListObservableStore)
+ : VectorViewModel(initialState) {
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: IncomingShareState): IncomingShareViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ @JvmStatic
+ override fun create(viewModelContext: ViewModelContext, state: IncomingShareState): IncomingShareViewModel? {
+ val activity: IncomingShareActivity = (viewModelContext as ActivityViewModelContext).activity()
+ return activity.incomingShareViewModelFactory.create(state)
+ }
+ }
+
+ init {
+ observeRoomSummaries()
+ }
+
+ private fun observeRoomSummaries() {
+ sessionObservableStore.observe()
+ .observeOn(AndroidSchedulers.mainThread())
+ .switchMap {
+ it.orNull()?.rx()?.liveRoomSummaries()
+ ?: Observable.just(emptyList())
+ }
+ .throttleLast(300, TimeUnit.MILLISECONDS)
+ .subscribe {
+ shareRoomListObservableStore.post(it)
+ }
+ .disposeOnClear()
+ }
+}
diff --git a/matrix-sdk-android-rx/src/test/java/im/vector/matrix/rx/ExampleUnitTest.java b/vector/src/main/java/im/vector/riotx/features/share/ShareRoomListObservableStore.kt
similarity index 61%
rename from matrix-sdk-android-rx/src/test/java/im/vector/matrix/rx/ExampleUnitTest.java
rename to vector/src/main/java/im/vector/riotx/features/share/ShareRoomListObservableStore.kt
index 6b7fcfe7e6..c46ec42d64 100644
--- a/matrix-sdk-android-rx/src/test/java/im/vector/matrix/rx/ExampleUnitTest.java
+++ b/vector/src/main/java/im/vector/riotx/features/share/ShareRoomListObservableStore.kt
@@ -14,20 +14,12 @@
* limitations under the License.
*/
-package im.vector.matrix.rx;
+package im.vector.riotx.features.share
-import org.junit.Test;
+import im.vector.matrix.android.api.session.room.model.RoomSummary
+import im.vector.riotx.core.utils.RxStore
+import javax.inject.Inject
+import javax.inject.Singleton
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file
+@Singleton
+class ShareRoomListObservableStore @Inject constructor() : RxStore>()
diff --git a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
index 2edb59104b..40a14b3e6f 100644
--- a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
@@ -28,7 +28,6 @@ import androidx.core.graphics.drawable.DrawableCompat
import androidx.preference.PreferenceManager
import im.vector.riotx.R
import timber.log.Timber
-import java.util.*
/**
* Util class for managing themes.
@@ -131,24 +130,16 @@ object ThemeUtils {
*/
@ColorInt
fun getColor(c: Context, @AttrRes colorAttribute: Int): Int {
- if (mColorByAttr.containsKey(colorAttribute)) {
- return mColorByAttr[colorAttribute] as Int
+ return mColorByAttr.getOrPut(colorAttribute) {
+ try {
+ val color = TypedValue()
+ c.theme.resolveAttribute(colorAttribute, color, true)
+ color.data
+ } catch (e: Exception) {
+ Timber.e(e, "Unable to get color")
+ ContextCompat.getColor(c, android.R.color.holo_red_dark)
+ }
}
-
- var matchedColor: Int
-
- try {
- val color = TypedValue()
- c.theme.resolveAttribute(colorAttribute, color, true)
- matchedColor = color.data
- } catch (e: Exception) {
- Timber.e(e, "Unable to get color")
- matchedColor = ContextCompat.getColor(c, android.R.color.holo_red_dark)
- }
-
- mColorByAttr[colorAttribute] = matchedColor
-
- return matchedColor
}
fun getAttribute(c: Context, @AttrRes attribute: Int): TypedValue? {
diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml
index ab2c40c313..6661674edb 100644
--- a/vector/src/main/res/layout/fragment_room_detail.xml
+++ b/vector/src/main/res/layout/fragment_room_detail.xml
@@ -86,7 +86,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml
index 2af86d6c0d..f713561084 100644
--- a/vector/src/main/res/layout/view_attachment_type_selector.xml
+++ b/vector/src/main/res/layout/view_attachment_type_selector.xml
@@ -30,10 +30,12 @@
android:id="@+id/attachmentCameraButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_camera_white_24dp"
+ android:contentDescription="@string/attachment_type_camera"
tools:background="@color/colorAccent" />
@@ -50,10 +52,12 @@
android:id="@+id/attachmentGalleryButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_gallery_white_24dp"
+ android:contentDescription="@string/attachment_type_gallery"
tools:background="@color/colorAccent" />
@@ -70,10 +74,12 @@
android:id="@+id/attachmentFileButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_file_white_24dp"
+ android:contentDescription="@string/attachment_type_file"
tools:background="@color/colorAccent" />
@@ -99,10 +105,12 @@
android:id="@+id/attachmentAudioButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_audio_white_24dp"
+ android:contentDescription="@string/attachment_type_audio"
tools:background="@color/colorAccent" />
@@ -119,10 +127,12 @@
android:id="@+id/attachmentContactButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_contact_white_24dp"
+ android:contentDescription="@string/attachment_type_contact"
tools:background="@color/colorAccent" />
@@ -139,14 +149,16 @@
android:id="@+id/attachmentStickersButton"
style="@style/AttachmentTypeSelectorButton"
android:src="@drawable/ic_attachment_stickers_white_24dp"
+ android:contentDescription="@string/attachment_type_sticker"
tools:background="@color/colorAccent" />
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000000..121589a6cd
--- /dev/null
+++ b/vector/src/main/res/values-sr/strings.xml
@@ -0,0 +1,113 @@
+
+
+ sr
+ RS
+ Cyrl
+
+ Светла тема
+ Тамна тема
+ Црна тема
+ Status.im тема
+
+ Иницијализација сервиса
+ Синхронизација у току…
+ Бучна обавештења
+ Тиха обавештења
+
+ Поруке
+ Соба
+ Подешавања
+ Подаци о члану
+ Историјски
+ Пријава грешке
+ Пошаљи налепницу
+ Резервна копија кључева
+ Користи резервну копију кључева
+ Верификуј уређај
+
+ Креирање резервне копије кључева се није завршило, молим сачекајте…
+ Изгубићете ваше шифроване поруке ако се сад одјавите
+ Креирање резервне копије кључева је у току. Ако се одјавите сад, изгубићете приступ вашим шифрованим порукама.
+ Сигурносна копија кључева би требало да буде активна на свим вашим уређајима како би избегли губитак приступа вашим шифрованим порукама.
+ Не желим моје шифроване поруке
+ Прављење резервне копије кључева у току…
+ Користи резервну копију кључева
+ Да ли сте сигурни\?
+ Изгубићете приступ вашим шифрованим порукама уколико не направите резервну копију кључева пре него што се одјавите.
+
+ Учитавање…
+
+ У реду
+ Откажи
+ Сачувај
+ Напусти
+ Остани
+ Пошаљи
+ Копирај
+ Пошаљи поново
+ Уклони
+ Подели
+ Прихвати
+ Прескочи
+ Готово
+ Обустави
+ Игнориши
+ Прегледај
+ Одбаци
+
+ Изађи
+ Акције
+ Одјави се
+ Да ли сте сигурни да желите да се одјавите\?
+ Гласовни позив
+ Видео позив
+ Глобална претрага
+ Означи све као прочитано
+ Брзи одговор
+ Означи као прочитано
+ Отвори
+ Затвори
+ Онемогући
+
+ Потврда
+ Упозорење
+ Грешка
+
+ Омиљено
+ Људи
+ Собе
+ Позивнице
+ Низак приоритет
+ Разговори
+ Локални адресар
+ Листа корисника
+ Само Matrix контакти
+ Нема резултата
+ Нема подешених сервера идентитета.
+
+ Собе
+ Листа соба
+ Нема соба
+ Пошаљи позивницу
+ Опишите ваш проблем овде
+ Прочитај
+
+ Придружи се соби
+ Корисничко име
+ Направи налог
+ Пријави се
+ Одјави се
+ Пошаљи налепницу
+ Направи фотографију или видео снимак
+ Направи фотографију
+ Направи видео снимак
+
+ Пријави се
+ Пријави се помоћу single sign-on
+ Направи налог
+ Прескочи
+ Адреса електронске поште или корисничко име
+ Лозинка
+ Нова лозинка
+ Корисничко име
+
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index c627d40eb0..95ad9729b9 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -8,5 +8,6 @@
"Mute"
"Settings"
"Leave the room"
+ "%1$s made no changes"
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index 11209b5345..96471cfebe 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -39,7 +39,7 @@
android:title="@string/settings_send_typing_notifs" />