Merge branch 'develop' into feature/notif

This commit is contained in:
Benoit Marty 2020-03-06 14:43:23 +01:00 committed by GitHub
commit 23862cb3d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
228 changed files with 579 additions and 690 deletions

View File

@ -1,95 +0,0 @@
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
# Last docker plugin version can be found here:
# 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)
steps:
- label: "Compile and run Unit 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 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:
- "vector/build/outputs/apk/gplay/debug/*.apk"
branches: "!master"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble FDroid 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 lintFdroidRelease assembleFdroidDebug --stacktrace"
artifact_paths:
- "vector/build/outputs/apk/fdroid/debug/*.apk"
branches: "!master"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Build Google Play unsigned APK"
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 assembleGplayRelease --stacktrace"
artifact_paths:
- "vector/build/outputs/apk/gplay/release/*.apk"
branches: "master"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
# Code quality
- label: "Code quality"
command:
- "./tools/check/check_code_quality.sh"
- label: "ktlint"
command:
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
- "./ktlint --android --experimental -v"
plugins:
- docker#v3.1.0:
image: "openjdk"
# Check that indonesians files are identical.
# Due to Android issue, the resource folder must be values-in/, and Weblate export data into values-id/.
# If this step fails, it means that Weblate has updated the file in value-id/ so to fix it, copy the file to values-in/
- label: "Indonesian"
command:
- "diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml"

View File

@ -1,9 +1,6 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="160" /> <option name="RIGHT_MARGIN" value="160" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value> <value>

View File

@ -49,12 +49,12 @@ script:
# Build Android test (assembleAndroidTest) (disabled for now) # Build Android test (assembleAndroidTest) (disabled for now)
# Code quality (lintGplayRelease lintFdroidRelease) # Code quality (lintGplayRelease lintFdroidRelease)
# Split into two steps because if a task contain Fdroid, PlayService will be disabled # Split into two steps because if a task contain Fdroid, PlayService will be disabled
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace # Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace # Done by Buildkite now: - ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370) # Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
# - ./gradlew testGplayReleaseUnitTest --stacktrace # - ./gradlew testGplayReleaseUnitTest --stacktrace
# Other code quality check # Other code quality check
- ./tools/check/check_code_quality.sh # Done by Buildkite now: - ./tools/check/check_code_quality.sh
- ./tools/travis/check_pr.sh - ./tools/travis/check_pr.sh
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/. # Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml # Done by Buildkite now: - diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml

View File

@ -5,10 +5,17 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- Share image and other media from e2e rooms (#677)
- Add support for `/plain` command (#12)
- Detect spaces in password if user fail to login (#1038)
- FTUE: do not display a different color when encrypting message when not in developer mode.
- Open room member profile from avatar of the room member state event (#935)
- Restore the push rules configuration in the settings - Restore the push rules configuration in the settings
Bugfix 🐛: Bugfix 🐛:
- Fix crash on attachment preview screen (#1088) - Fix crash on attachment preview screen (#1088)
- "Share" option is not appearing in encrypted rooms for images (#1031)
- Set "image/jpeg" as MIME type of images instead of "image/jpg" (#1075)
Translations 🗣: Translations 🗣:
- -
@ -17,10 +24,12 @@ SDK API changes ⚠️:
- PushRuleService.getPushRules() now returns a RuleSet. Use getAllRules() on this object to get all the rules. - PushRuleService.getPushRules() now returns a RuleSet. Use getAllRules() on this object to get all the rules.
Build 🧱: Build 🧱:
- - Upgrade ktlint to version 0.36.0
- Pipeline file for Buildkite is now hosted on another Github repository: https://github.com/matrix-org/pipelines/blob/master/riotx-android/pipeline.yml
Other changes: Other changes:
- - Restore availability to Chromebooks (#932)
- Add a [documentation](./docs/integration_tests.md) to run integration tests
Changes in RiotX 0.17.0 (2020-02-27) Changes in RiotX 0.17.0 (2020-02-27)
=================================================== ===================================================

View File

@ -82,6 +82,8 @@ Make sure the following commands execute without any error:
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices. RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient. Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
You should consider adding Unit tests with your PR, and also integration tests (AndroidTest). Please refer to [this document](./docs/integration_tests.md) to install and run the integration test environment.
### Internationalisation ### Internationalisation
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/). 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/).

97
docs/integration_tests.md Normal file
View File

@ -0,0 +1,97 @@
# Integration tests
Integration tests are useful to ensure that the code works well for any use cases.
They can also be used as sample on how to use the Matrix SDK.
In a ideal world, every API of the SDK should be covered by integration tests. For the moment, we have test mainly for the Crypto part, which is the tricky part. But it covers quite a lot of features: accounts creation, login to existing account, send encrypted messages, keys backup, verification, etc.
The Matrix SDK is able to open multiple sessions, for the same user, of for different users. This way we can test communication between several sessions on a single device.
## Pre requirements
Integration tests need a homeserver running on localhost.
The documentation describes what we do to have one, using [Synapse](https://github.com/matrix-org/synapse/), which is the Matrix reference homeserver.
## Install and run Synapse
Steps:
- Install virtualenv
```bash
python3 -m pip install virtualenv
```
- Clone Synapse repository
```bash
git clone -b develop https://github.com/matrix-org/synapse.git
```
or
```bash
git clone -b develop git@github.com:matrix-org/synapse.git
```
You should have the develop branch cloned by default.
- Run synapse, from the Synapse folder you just cloned
```bash
virtualenv -p python3 env
source env/bin/activate
pip install -e .
demo/start.sh --no-rate-limit
```
Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `pip install -e .`:
```bash
pip install matrix-synapse
```
You should now have 3 running federated Synapse instances 🎉, at http://127.0.0.1:8080/, http://127.0.0.1:8081/ and http://127.0.0.1:8082/, which should display a "It Works! Synapse is running" message.
## Run the test
It's recommended to run tests using an Android Emulator and not a real device. First reason for that is that the tests will use http://10.0.2.2:8080 to connect to Synapse, which run locally on your machine.
You can run all the tests in the `androidTest` folders.
## Stop Synapse
To stop Synapse, you can run the following commands:
```bash
./demo/stop.sh
```
And you can deactivate the virtualenv:
```bash
deactivate
```
## Troubleshoot
You'll need python3 to be able to run synapse
### Android Emulator does cannot reach the homeserver
Try on the Emulator browser to open "http://10.0.2.2:8080". You should see the "Synapse is running" message.
### virtualenv command fails
You can try using
```bash
python3 -m venv env
```
or
```bash
python3 -m virtualenv env
```
instead of
```bash
virtualenv -p python3 env
```

View File

@ -126,7 +126,7 @@ dependencies {
kapt 'dk.ilios:realmfieldnameshelper:1.1.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
// Work // Work
implementation "androidx.work:work-runtime-ktx:2.3.0" implementation "androidx.work:work-runtime-ktx:2.3.3"
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"

View File

@ -31,7 +31,7 @@ data class ContentAttachmentData(
val name: String? = null, val name: String? = null,
val queryUri: String, val queryUri: String,
val path: String, val path: String,
val mimeType: String?, private val mimeType: String?,
val type: Type val type: Type
) : Parcelable { ) : Parcelable {
@ -41,4 +41,6 @@ data class ContentAttachmentData(
AUDIO, AUDIO,
VIDEO VIDEO
} }
fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
} }

View File

@ -34,7 +34,11 @@ interface FileService {
/** /**
* Download file in cache * Download file in cache
*/ */
FOR_INTERNAL_USE FOR_INTERNAL_USE,
/**
* Download file in file provider path
*/
FOR_EXTERNAL_SHARE
} }
/** /**

View File

@ -51,4 +51,4 @@ data class MessageAudioContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent ) : MessageWithAttachmentContent

View File

@ -57,7 +57,7 @@ data class MessageFileContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent { ) : MessageWithAttachmentContent {
fun getMimeType(): String { fun getMimeType(): String {
// Mimetype default to plain text, should not be used // Mimetype default to plain text, should not be used

View File

@ -20,6 +20,6 @@ package im.vector.matrix.android.api.session.room.model.message
/** /**
* A content with image information * A content with image information
*/ */
interface MessageImageInfoContent : MessageEncryptedContent { interface MessageImageInfoContent : MessageWithAttachmentContent {
val info: ImageInfo? val info: ImageInfo?
} }

View File

@ -51,4 +51,4 @@ data class MessageVideoContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent ) : MessageWithAttachmentContent

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
/** /**
* Interface for message which can contains an encrypted file * Interface for message which can contains an encrypted file
*/ */
interface MessageEncryptedContent : MessageContent { interface MessageWithAttachmentContent : MessageContent {
/** /**
* Required if the file is unencrypted. The URL (typically MXC URI) to the image. * Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/ */
@ -36,4 +36,4 @@ interface MessageEncryptedContent : MessageContent {
/** /**
* Get the url of the encrypted file or of the file * Get the url of the encrypted file or of the file
*/ */
fun MessageEncryptedContent.getFileUrl() = encryptedFileInfo?.url ?: url fun MessageWithAttachmentContent.getFileUrl() = encryptedFileInfo?.url ?: url

View File

@ -25,3 +25,7 @@ annotation class SessionFilesDirectory
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SessionCacheDirectory annotation class SessionCacheDirectory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheDirectory

View File

@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import java.io.File
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class]) @Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class])
@MatrixScope @MatrixScope
@ -52,6 +53,9 @@ internal interface MatrixComponent {
fun resources(): Resources fun resources(): Resources
@CacheDirectory
fun cacheDir(): File
fun olmManager(): OlmManager fun olmManager(): OlmManager
fun taskExecutor(): TaskExecutor fun taskExecutor(): TaskExecutor

View File

@ -26,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import java.io.File
import java.util.concurrent.Executors import java.util.concurrent.Executors
@Module @Module
@ -49,6 +50,13 @@ internal object MatrixModule {
return context.resources return context.resources
} }
@JvmStatic
@Provides
@CacheDirectory
fun providesCacheDir(context: Context): File {
return context.cacheDir
}
@JvmStatic @JvmStatic
@Provides @Provides
@MatrixScope @MatrixScope

View File

@ -24,11 +24,11 @@ import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.di.CacheDirectory
import im.vector.matrix.android.internal.di.SessionCacheDirectory import im.vector.matrix.android.internal.di.SessionCacheDirectory
import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5
import im.vector.matrix.android.internal.util.toCancelable import im.vector.matrix.android.internal.util.toCancelable
import im.vector.matrix.android.internal.util.writeToFile import im.vector.matrix.android.internal.util.writeToFile
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -42,8 +42,10 @@ import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
internal class DefaultFileService @Inject constructor( internal class DefaultFileService @Inject constructor(
@SessionCacheDirectory @CacheDirectory
private val cacheDirectory: File, private val cacheDirectory: File,
@SessionCacheDirectory
private val sessionCacheDirectory: File,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
@Unauthenticated @Unauthenticated
private val okHttpClient: OkHttpClient, private val okHttpClient: OkHttpClient,
@ -62,13 +64,14 @@ internal class DefaultFileService @Inject constructor(
return GlobalScope.launch(coroutineDispatchers.main) { return GlobalScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
Try { Try {
val folder = getFolder(downloadMode, id) val folder = File(sessionCacheDirectory, "MF")
if (!folder.exists()) {
folder.mkdirs()
}
File(folder, fileName) File(folder, fileName)
}.flatMap { destFile -> }.flatMap { destFile ->
if (!destFile.exists() || downloadMode == FileService.DownloadMode.TO_EXPORT) { if (!destFile.exists()) {
Try { val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
val request = Request.Builder() val request = Request.Builder()
.url(resolvedUrl) .url(resolvedUrl)
@ -77,45 +80,34 @@ internal class DefaultFileService @Inject constructor(
val response = okHttpClient.newCall(request).execute() val response = okHttpClient.newCall(request).execute()
var inputStream = response.body?.byteStream() var inputStream = response.body?.byteStream()
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}") Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
if (!response.isSuccessful if (!response.isSuccessful || inputStream == null) {
|| inputStream == null) { return@flatMap Try.Failure(IOException())
throw IOException()
} }
if (elementToDecrypt != null) { if (elementToDecrypt != null) {
Timber.v("## decrypt file") Timber.v("## decrypt file")
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
?: throw IllegalStateException("Decryption error") ?: return@flatMap Try.Failure(IllegalStateException("Decryption error"))
} }
writeToFile(inputStream, destFile) writeToFile(inputStream, destFile)
destFile
}
} else {
Try.just(destFile)
} }
Try.just(copyFile(destFile, downloadMode))
} }
} }
.foldToCallback(callback) .foldToCallback(callback)
}.toCancelable() }.toCancelable()
} }
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File { private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File {
return when (downloadMode) { return when (downloadMode) {
FileService.DownloadMode.FOR_INTERNAL_USE -> { FileService.DownloadMode.TO_EXPORT ->
// Create dir tree (MF stands for Matrix File): file.copyTo(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), file.name), true)
// <cache>/<sessionId>/MF/<md5(id)>/ FileService.DownloadMode.FOR_EXTERNAL_SHARE ->
val tmpFolderSession = File(cacheDirectory, "MF") file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true)
File(tmpFolderSession, id.md5()) FileService.DownloadMode.FOR_INTERNAL_USE ->
} file
FileService.DownloadMode.TO_EXPORT -> {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
}
}
.also { folder ->
if (!folder.exists()) {
folder.mkdirs()
}
} }
} }
} }

View File

@ -20,6 +20,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
@ -33,7 +34,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
val listeners = listeners.getOrPut(key) { ArrayList() } val listeners = listeners.getOrPut(key) { ArrayList() }
listeners.add(updateListener) listeners.add(updateListener)
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
mainHandler.post { updateListener.onUpdate(currentState) } mainHandler.post {
try {
updateListener.onUpdate(currentState)
} catch (e: Exception) {
Timber.e(e, "## ContentUploadStateTracker.onUpdate() failed")
}
}
} }
override fun untrack(key: String, updateListener: ContentUploadStateTracker.UpdateListener) { override fun untrack(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
@ -79,7 +86,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
private fun updateState(key: String, state: ContentUploadStateTracker.State) { private fun updateState(key: String, state: ContentUploadStateTracker.State) {
states[key] = state states[key] = state
mainHandler.post { mainHandler.post {
listeners[key]?.forEach { it.onUpdate(state) } listeners[key]?.forEach {
try {
it.onUpdate(state)
} catch (e: Exception) {
Timber.e(e, "## ContentUploadStateTracker.onUpdate() failed")
}
}
} }
} }
} }

View File

@ -58,7 +58,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
override val sessionId: String, override val sessionId: String,
val events: List<Event>, val events: List<Event>,
val attachment: ContentAttachmentData, val attachment: ContentAttachmentData,
val isRoomEncrypted: Boolean, val isEncrypted: Boolean,
val compressBeforeSending: Boolean, val compressBeforeSending: Boolean,
override val lastFailureMessage: String? = null override val lastFailureMessage: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@ -90,9 +90,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
Timber.e(e) Timber.e(e)
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) } notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
return Result.success( return Result.success(
WorkerParamsFactory.toData(params.copy( WorkerParamsFactory.toData(
params.copy(
lastFailureMessage = e.localizedMessage lastFailureMessage = e.localizedMessage
)) )
)
) )
} }
.let { originalFile -> .let { originalFile ->
@ -136,7 +138,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
} }
try { try {
val contentUploadResponse = if (params.isRoomEncrypted) { val contentUploadResponse = if (params.isEncrypted) {
Timber.v("Encrypt thumbnail") Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
@ -174,18 +176,18 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
return try { return try {
val contentUploadResponse = if (params.isRoomEncrypted) { val contentUploadResponse = if (params.isEncrypted) {
Timber.v("Encrypt file") Timber.v("Encrypt file")
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType) val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader fileUploader
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
} else { } else {
fileUploader fileUploader
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener) .uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
} }
handleSuccess(params, handleSuccess(params,
@ -226,7 +228,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes) updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
} }
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isRoomEncrypted) val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isEncrypted)
return Result.success(WorkerParamsFactory.toData(sendParams)) return Result.success(WorkerParamsFactory.toData(sendParams))
} }

View File

@ -261,7 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_IMAGE, msgType = MessageType.MSGTYPE_IMAGE,
body = attachment.name ?: "image", body = attachment.name ?: "image",
info = ImageInfo( info = ImageInfo(
mimeType = attachment.mimeType, mimeType = attachment.getSafeMimeType(),
width = width?.toInt() ?: 0, width = width?.toInt() ?: 0,
height = height?.toInt() ?: 0, height = height?.toInt() ?: 0,
size = attachment.size.toInt() size = attachment.size.toInt()
@ -293,7 +293,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_VIDEO, msgType = MessageType.MSGTYPE_VIDEO,
body = attachment.name ?: "video", body = attachment.name ?: "video",
videoInfo = VideoInfo( videoInfo = VideoInfo(
mimeType = attachment.mimeType, mimeType = attachment.getSafeMimeType(),
width = width, width = width,
height = height, height = height,
size = attachment.size, size = attachment.size,
@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_AUDIO, msgType = MessageType.MSGTYPE_AUDIO,
body = attachment.name ?: "audio", body = attachment.name ?: "audio",
audioInfo = AudioInfo( audioInfo = AudioInfo(
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg", mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
size = attachment.size size = attachment.size
), ),
url = attachment.path url = attachment.path
@ -325,7 +325,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_FILE, msgType = MessageType.MSGTYPE_FILE,
body = attachment.name ?: "file", body = attachment.name ?: "file",
info = FileInfo( info = FileInfo(
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }
?: "application/octet-stream", ?: "application/octet-stream",
size = attachment.size size = attachment.size
), ),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:id="@+id/imageView_icon_and_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:src="@drawable/matrix_user" />
<TextView
android:id="@+id/textView_icon_and_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:singleLine="true"
android:textColor="@android:color/white"
tools:text="A text here" />
</LinearLayout>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/listView_icon_and_text"/>
</LinearLayout>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="#248801">
<org.matrix.androidsdk.view.AutoScrollDownListView
android:id="@+id/listView_messages"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="#190986"
android:cacheColorHint="@android:color/transparent"
android:childDivider="@android:color/transparent"
android:divider="#ffffff"
android:dividerHeight="0dp"
android:listSelector="@android:color/transparent"
android:transcriptMode="normal"
tools:layout_height="120dp" />
</FrameLayout>

View File

@ -1,3 +0,0 @@
<resources>
</resources>

View File

@ -296,7 +296,7 @@ dependencies {
implementation 'com.airbnb.android:mvrx:1.3.0' implementation 'com.airbnb.android:mvrx:1.3.0'
// Work // Work
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02" implementation "androidx.work:work-runtime-ktx:2.3.3"
// Paging // Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.1" implementation "androidx.paging:paging-runtime-ktx:2.1.1"

View File

@ -6,9 +6,15 @@
<issue id="MissingTranslation" severity="warning" /> <issue id="MissingTranslation" severity="warning" />
<issue id="TypographyEllipsis" severity="error" /> <issue id="TypographyEllipsis" severity="error" />
<issue id="ImpliedQuantity" severity="warning" /> <issue id="ImpliedQuantity" severity="warning" />
<issue id="IconXmlAndPng" severity="error" />
<issue id="IconDipSize" severity="error" />
<issue id="IconDuplicatesConfig" severity="error" />
<issue id="IconDuplicates" severity="error" />
<issue id="IconExpectedSize" severity="error" />
<!-- UX --> <!-- UX -->
<issue id="ButtonOrder" severity="error" /> <issue id="ButtonOrder" severity="error" />
<issue id="TextFields" severity="error" />
<!-- Layout --> <!-- Layout -->
<issue id="UnknownIdInLayout" severity="error" /> <issue id="UnknownIdInLayout" severity="error" />
@ -19,6 +25,7 @@
<issue id="InefficientWeight" severity="error" /> <issue id="InefficientWeight" severity="error" />
<issue id="DisableBaselineAlignment" severity="error" /> <issue id="DisableBaselineAlignment" severity="error" />
<issue id="ScrollViewSize" severity="error" /> <issue id="ScrollViewSize" severity="error" />
<issue id="NegativeMargin" severity="error" />
<!-- RTL --> <!-- RTL -->
<issue id="RtlEnabled" severity="error" /> <issue id="RtlEnabled" severity="error" />
@ -30,9 +37,21 @@
<issue id="SetTextI18n" severity="error" /> <issue id="SetTextI18n" severity="error" />
<issue id="ViewConstructor" severity="error" /> <issue id="ViewConstructor" severity="error" />
<issue id="UseValueOf" severity="error" /> <issue id="UseValueOf" severity="error" />
<issue id="Recycle" severity="error" />
<issue id="KotlinPropertyAccess" severity="error" />
<!-- Ignore error from HtmlCompressor lib --> <!-- Ignore error from HtmlCompressor lib -->
<issue id="InvalidPackage"> <issue id="InvalidPackage">
<ignore path="**/htmlcompressor-1.4.jar"/> <ignore path="**/htmlcompressor-1.4.jar" />
</issue> </issue>
<!-- Manifest -->
<issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="error" />
<!-- Timber -->
<issue id="BinaryOperationInTimber" severity="error" />
<!-- Wording -->
<!-- TODO When strings are imported from Weblate, move this to error -->
<issue id="Typos" severity="warning" />
</lint> </lint>

View File

@ -114,7 +114,7 @@ class DebugMenuActivity : VectorBaseActivity() {
.setContentText("Content") .setContentText("Content")
// No effect because it's a group summary notif // No effect because it's a group summary notif
.setNumber(33) .setNumber(33)
.setSmallIcon(R.drawable.logo_transparent) .setSmallIcon(R.drawable.ic_status_bar)
// This provocate the badge issue: no badge for group notification // This provocate the badge issue: no badge for group notification
.setGroup("GroupKey") .setGroup("GroupKey")
.setGroupSummary(true) .setGroupSummary(true)
@ -147,7 +147,7 @@ class DebugMenuActivity : VectorBaseActivity() {
// For shortcut on long press on launcher icon // For shortcut on long press on launcher icon
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE) .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
.setStyle(messagingStyle1) .setStyle(messagingStyle1)
.setSmallIcon(R.drawable.logo_transparent) .setSmallIcon(R.drawable.ic_status_bar)
.setGroup("GroupKey") .setGroup("GroupKey")
.build() .build()
) )
@ -159,7 +159,7 @@ class DebugMenuActivity : VectorBaseActivity() {
.setContentTitle("Title 2") .setContentTitle("Title 2")
.setContentText("Content 2") .setContentText("Content 2")
.setStyle(messagingStyle2) .setStyle(messagingStyle2)
.setSmallIcon(R.drawable.logo_transparent) .setSmallIcon(R.drawable.ic_status_bar)
.setGroup("GroupKey") .setGroup("GroupKey")
.build() .build()
) )

View File

@ -9,6 +9,15 @@
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
<!-- Tell that the Camera is not mandatory to install the application -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application <application
android:name=".VectorApplication" android:name=".VectorApplication"
android:allowBackup="false" android:allowBackup="false"

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("DEPRECATION")
package im.vector.riotx.core.hardware
import android.content.Context
import android.hardware.Camera
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import javax.inject.Inject
class HardwareInfo @Inject constructor(
private val context: Context
) {
/**
* Tell if the device has a back (or external) camera
*/
fun hasBackCamera(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return Camera.getNumberOfCameras() > 0
}
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager? ?: return Camera.getNumberOfCameras() > 0
return manager.cameraIdList.any {
val lensFacing = manager.getCameraCharacteristics(it).get(CameraCharacteristics.LENS_FACING)
lensFacing == CameraCharacteristics.LENS_FACING_BACK || lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL
}
}
}

View File

@ -129,7 +129,7 @@ class BadgeFloatingActionButton @JvmOverloads constructor(
attrs?.let { initAttrs(attrs) } attrs?.let { initAttrs(attrs) }
} }
@SuppressWarnings("ResourceType", "Recycle") @SuppressWarnings("Recycle")
private fun initAttrs(attrs: AttributeSet) { private fun initAttrs(attrs: AttributeSet) {
context.obtainStyledAttributes(attrs, R.styleable.BadgeFloatingActionButton).use { context.obtainStyledAttributes(attrs, R.styleable.BadgeFloatingActionButton).use {
counterBackgroundColor = it.getColor(R.styleable.BadgeFloatingActionButton_badgeBackgroundColor, 0) counterBackgroundColor = it.getColor(R.styleable.BadgeFloatingActionButton_badgeBackgroundColor, 0)

View File

@ -144,10 +144,10 @@ fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): Strin
Timber.e("Cannot use the external storage media to save image") Timber.e("Cannot use the external storage media to save image")
} }
} catch (uoe: UnsupportedOperationException) { } catch (uoe: UnsupportedOperationException) {
Timber.e(uoe, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI " + Timber.e(uoe, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI.")
"no SD card? Attempting to insert into device storage.") Timber.e("no SD card? Attempting to insert into device storage.")
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI. $e") Timber.e(e, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI.")
} }
if (null == dummyUri) { if (null == dummyUri) {
@ -157,13 +157,13 @@ fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): Strin
Timber.e("Cannot use the internal storage to save media to save image") Timber.e("Cannot use the internal storage to save media to save image")
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Unable to insert camera URI into internal storage. Giving up. $e") Timber.e(e, "Unable to insert camera URI into internal storage. Giving up.")
} }
} }
if (dummyUri != null) { if (dummyUri != null) {
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, dummyUri) captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, dummyUri)
Timber.v("trying to take a photo on " + dummyUri.toString()) Timber.v("trying to take a photo on $dummyUri")
} else { } else {
Timber.v("trying to take a photo with no predefined uri") Timber.v("trying to take a photo with no predefined uri")
} }

View File

@ -27,6 +27,7 @@ import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_CAMERA
import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE
import com.kbeanie.multipicker.core.ImagePickerImpl import com.kbeanie.multipicker.core.ImagePickerImpl
import com.kbeanie.multipicker.core.PickerManager import com.kbeanie.multipicker.core.PickerManager
import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.core.platform.Restorable import im.vector.riotx.core.platform.Restorable
@ -176,13 +177,13 @@ class AttachmentsHelper private constructor(private val context: Context,
fun handleShareIntent(intent: Intent): Boolean { fun handleShareIntent(intent: Intent): Boolean {
val type = intent.resolveType(context) ?: return false val type = intent.resolveType(context) ?: return false
if (type.startsWith("image")) { if (type.startsWith("image")) {
imagePicker.submit(intent) imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
} else if (type.startsWith("video")) { } else if (type.startsWith("video")) {
videoPicker.submit(intent) videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
} else if (type.startsWith("audio")) { } else if (type.startsWith("audio")) {
videoPicker.submit(intent) videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
filePicker.submit(intent) filePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
} else { } else {
return false return false
} }

View File

@ -23,6 +23,6 @@ import im.vector.matrix.android.api.session.content.ContentAttachmentData
*/ */
fun ContentAttachmentData.isEditable(): Boolean { fun ContentAttachmentData.isEditable(): Boolean {
return type == ContentAttachmentData.Type.IMAGE return type == ContentAttachmentData.Type.IMAGE
&& mimeType?.startsWith("image/") == true && getSafeMimeType()?.startsWith("image/") == true
&& mimeType != "image/gif" && getSafeMimeType() != "image/gif"
} }

View File

@ -43,6 +43,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler), SPOILER("/spoiler", "<message>", R.string.command_description_spoiler),
POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll), POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll),
SHRUG("/shrug", "<message>", R.string.command_description_shrug), SHRUG("/shrug", "<message>", R.string.command_description_shrug),
PLAIN("/plain", "<message>", R.string.command_description_plain),
// TODO temporary command // TODO temporary command
VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify); VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify);

View File

@ -57,6 +57,15 @@ object CommandParser {
} }
return when (val slashCommand = messageParts.first()) { return when (val slashCommand = messageParts.first()) {
Command.PLAIN.command -> {
val text = textMessage.substring(Command.PLAIN.command.length).trim()
if (text.isNotEmpty()) {
ParsedCommand.SendPlainText(text)
} else {
ParsedCommand.ErrorSyntax(Command.PLAIN)
}
}
Command.CHANGE_DISPLAY_NAME.command -> { Command.CHANGE_DISPLAY_NAME.command -> {
val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim() val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()

View File

@ -33,6 +33,7 @@ sealed class ParsedCommand {
// Valid commands: // Valid commands:
class SendPlainText(val message: CharSequence) : ParsedCommand()
class SendEmote(val message: CharSequence) : ParsedCommand() class SendEmote(val message: CharSequence) : ParsedCommand()
class SendRainbow(val message: CharSequence) : ParsedCommand() class SendRainbow(val message: CharSequence) : ParsedCommand()
class SendRainbowEmote(val message: CharSequence) : ParsedCommand() class SendRainbowEmote(val message: CharSequence) : ParsedCommand()

View File

@ -36,8 +36,8 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
// TODO Import mLanguageReceiver From Riot? // TODO Import mLanguageReceiver From Riot?
fun onConfigurationChanged() { fun onConfigurationChanged() {
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) { if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
Timber.v("## onConfigurationChanged() : the locale has been updated to " + Locale.getDefault().toString() Timber.v("## onConfigurationChanged(): the locale has been updated to ${Locale.getDefault()}")
+ ", restore the expected value " + VectorLocale.applicationLocale.toString()) Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}")
updateApplicationSettings(VectorLocale.applicationLocale, updateApplicationSettings(VectorLocale.applicationLocale,
FontScale.getFontScalePrefValue(context), FontScale.getFontScalePrefValue(context),
ThemeUtils.getApplicationTheme(context)) ThemeUtils.getApplicationTheme(context))

View File

@ -74,7 +74,7 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel
isIndeterminate = true)) isIndeterminate = true))
} }
is StepProgressListener.Step.ImportingKey -> { is StepProgressListener.Step.ImportingKey -> {
Timber.d("backupKeys.ImportingKey.progress: " + step.progress) Timber.d("backupKeys.ImportingKey.progress: ${step.progress}")
// Progress 0 can take a while, display an indeterminate progress in this case // Progress 0 can take a while, display an indeterminate progress in this case
if (step.progress == 0) { if (step.progress == 0) {
sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)

View File

@ -1,29 +0,0 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
val supportedVerificationMethods =
listOf(
// RiotX supports SAS verification
VerificationMethod.SAS,
// RiotX is able to show QR codes
VerificationMethod.QR_CODE_SHOW,
// RiotX is able to scan QR codes
VerificationMethod.QR_CODE_SCAN
)

View File

@ -0,0 +1,47 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.riotx.core.hardware.HardwareInfo
import timber.log.Timber
import javax.inject.Inject
class SupportedVerificationMethodsProvider @Inject constructor(
private val hardwareInfo: HardwareInfo
) {
/**
* Provide the list of supported method by RiotX, with or without the QR_CODE_SCAN, depending if a back camera
* is available
*/
fun provide(): List<VerificationMethod> {
return mutableListOf(
// RiotX supports SAS verification
VerificationMethod.SAS,
// RiotX is able to show QR codes
VerificationMethod.QR_CODE_SHOW)
.apply {
if (hardwareInfo.hasBackCamera()) {
// RiotX is able to scan QR codes, and a Camera is available
add(VerificationMethod.QR_CODE_SCAN)
} else {
// This quite uncommon
Timber.w("No back Camera detected")
}
}
}
}

View File

@ -63,9 +63,11 @@ data class VerificationBottomSheetViewState(
val isMe: Boolean = false val isMe: Boolean = false
) : MvRxState ) : MvRxState
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState, class VerificationBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: VerificationBottomSheetViewState,
@Assisted args: VerificationBottomSheet.VerificationArgs, @Assisted args: VerificationBottomSheet.VerificationArgs,
private val session: Session) private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider)
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState), : VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
VerificationService.Listener { VerificationService.Listener {
@ -116,9 +118,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
if (autoReady) { if (autoReady) {
// TODO, can I be here in DM mode? in this case should test if roomID is null? // TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService() session.cryptoService().verificationService()
.readyPendingVerification(supportedVerificationMethods, .readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr!!.otherUserId, pr!!.otherUserId,
pr.transactionId ?: "") pr.transactionId ?: ""
)
} }
} }
@ -173,7 +177,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
session session
.cryptoService() .cryptoService()
.verificationService() .verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId) .requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
otherUserId,
data,
pendingLocalId
)
) )
) )
} }
@ -191,7 +200,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
pendingRequest = Success(session pendingRequest = Success(session
.cryptoService() .cryptoService()
.verificationService() .verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId) .requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
) )
) )
} }
@ -294,8 +303,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage)) _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage))
} }
Unit
} }
}.exhaustive }.exhaustive
} }
@ -362,9 +369,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
// auto ready in this case, as we are waiting // auto ready in this case, as we are waiting
// TODO, can I be here in DM mode? in this case should test if roomID is null? // TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService() session.cryptoService().verificationService()
.readyPendingVerification(supportedVerificationMethods, .readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr.otherUserId, pr.otherUserId,
pr.transactionId ?: "") pr.transactionId ?: ""
)
} }
// Use this one! // Use this one!

View File

@ -37,6 +37,7 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.util.Pair import androidx.core.util.Pair
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -57,17 +58,17 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.ImageLoader
import com.google.android.material.checkbox.MaterialCheckBox import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
@ -77,12 +78,14 @@ import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoC
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
@ -93,6 +96,7 @@ import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.extensions.showKeyboard import im.vector.riotx.core.extensions.showKeyboard
import im.vector.riotx.core.files.addEntryToDownloadManager import im.vector.riotx.core.files.addEntryToDownloadManager
import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.intent.getMimeTypeFromUri
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.ui.views.JumpToReadMarkerView import im.vector.riotx.core.ui.views.JumpToReadMarkerView
@ -1124,6 +1128,23 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState) roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
} }
private fun onShareActionClicked(action: EventSharedAction.Share) {
session.downloadFile(
FileService.DownloadMode.FOR_EXTERNAL_SHARE,
action.eventId,
action.messageContent.body,
action.messageContent.getFileUrl(),
action.messageContent.encryptedFileInfo?.toElementToDecrypt(),
object : MatrixCallback<File> {
override fun onSuccess(data: File) {
if (isAdded) {
shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri()))
}
}
}
)
}
private fun handleActions(action: EventSharedAction) { private fun handleActions(action: EventSharedAction) {
when (action) { when (action) {
is EventSharedAction.OpenUserProfile -> { is EventSharedAction.OpenUserProfile -> {
@ -1145,32 +1166,7 @@ class RoomDetailFragment @Inject constructor(
promptConfirmationToRedactEvent(action) promptConfirmationToRedactEvent(action)
} }
is EventSharedAction.Share -> { is EventSharedAction.Share -> {
// TODO current data communication is too limited onShareActionClicked(action)
// Need to now the media type
// TODO bad, just POC
BigImageViewer.imageLoader().loadImage(
action.hashCode(),
Uri.parse(action.imageUrl),
object : ImageLoader.Callback {
override fun onFinish() {}
override fun onSuccess(image: File?) {
if (image != null) {
shareMedia(requireContext(), image, "image/*")
}
}
override fun onFail(error: Exception?) {}
override fun onCacheHit(imageType: Int, image: File?) {}
override fun onCacheMiss(imageType: Int, image: File?) {}
override fun onProgress(progress: Int) {}
override fun onStart() {}
}
)
} }
is EventSharedAction.ViewEditHistory -> { is EventSharedAction.ViewEditHistory -> {
onEditedDecorationClicked(action.messageInformationData) onEditedDecorationClicked(action.messageInformationData)

View File

@ -65,7 +65,7 @@ import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.home.room.typing.TypingHelper
@ -81,13 +81,15 @@ import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, class RoomDetailViewModel @AssistedInject constructor(
@Assisted initialState: RoomDetailViewState,
userPreferencesProvider: UserPreferencesProvider, userPreferencesProvider: UserPreferencesProvider,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val typingHelper: TypingHelper, private val typingHelper: TypingHelper,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val session: Session private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener { ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -340,6 +342,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is ParsedCommand.ErrorUnknownSlashCommand -> { is ParsedCommand.ErrorUnknownSlashCommand -> {
_viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) _viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand))
} }
is ParsedCommand.SendPlainText -> {
// Send the text message to the room, without markdown
room.sendTextMessage(slashCommandResult.message, autoMarkdown = false)
_viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft()
}
is ParsedCommand.Invite -> { is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult) handleInviteSlashCommand(slashCommandResult)
popDraft() popDraft()
@ -421,7 +429,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
session session
.cryptoService() .cryptoService()
.verificationService() .verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId) .requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), slashCommandResult.userId, room.roomId)
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft() popDraft()
} }
@ -828,7 +836,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
if (session.cryptoService().verificationService().readyPendingVerificationInDMs( if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
supportedVerificationMethods, supportedVerificationMethodsProvider.provide(),
action.otherUserId, action.otherUserId,
room.roomId, room.roomId,
action.transactionId)) { action.transactionId)) {

View File

@ -0,0 +1,58 @@
/*
* 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
import androidx.annotation.ColorInt
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Inject
class MessageColorProvider @Inject constructor(
private val colorProvider: ColorProvider,
private val vectorPreferences: VectorPreferences) {
@ColorInt
fun getMessageTextColor(sendState: SendState): Int {
return if (vectorPreferences.developerMode()) {
when (sendState) {
// SendStates, in the classical order they will occur
SendState.UNKNOWN,
SendState.UNSENT -> colorProvider.getColorFromAttribute(R.attr.vctr_sending_message_text_color)
SendState.ENCRYPTING -> colorProvider.getColorFromAttribute(R.attr.vctr_encrypting_message_text_color)
SendState.SENDING -> colorProvider.getColorFromAttribute(R.attr.vctr_sending_message_text_color)
SendState.SENT,
SendState.SYNCED -> colorProvider.getColorFromAttribute(R.attr.vctr_message_text_color)
SendState.UNDELIVERED,
SendState.FAILED_UNKNOWN_DEVICES -> colorProvider.getColorFromAttribute(R.attr.vctr_unsent_message_text_color)
}
} else {
// When not in developer mode, we do not use special color for the encrypting state
when (sendState) {
SendState.UNKNOWN,
SendState.UNSENT,
SendState.ENCRYPTING,
SendState.SENDING -> colorProvider.getColorFromAttribute(R.attr.vctr_sending_message_text_color)
SendState.SENT,
SendState.SYNCED -> colorProvider.getColorFromAttribute(R.attr.vctr_message_text_color)
SendState.UNDELIVERED,
SendState.FAILED_UNKNOWN_DEVICES -> colorProvider.getColorFromAttribute(R.attr.vctr_unsent_message_text_color)
}
}
}
}

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorSharedAction import im.vector.riotx.core.platform.VectorSharedAction
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
@ -46,7 +47,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
data class Reply(val eventId: String) : data class Reply(val eventId: String) :
EventSharedAction(R.string.reply, R.drawable.ic_reply) EventSharedAction(R.string.reply, R.drawable.ic_reply)
data class Share(val imageUrl: String) : data class Share(val eventId: String, val messageContent: MessageWithAttachmentContent) :
EventSharedAction(R.string.share, R.drawable.ic_share) EventSharedAction(R.string.share, R.drawable.ic_share)
data class Resend(val eventId: String) : data class Resend(val eventId: String) :

View File

@ -29,8 +29,8 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.isTextMessage import im.vector.matrix.android.api.session.events.model.isTextMessage
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.model.message.MessageFormat import im.vector.matrix.android.api.session.room.model.message.MessageFormat
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
@ -260,13 +260,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.ViewEditHistory(informationData)) add(EventSharedAction.ViewEditHistory(informationData))
} }
if (canShare(msgType)) { if (canShare(msgType) && messageContent is MessageWithAttachmentContent) {
if (messageContent is MessageImageContent) { add(EventSharedAction.Share(timelineEvent.eventId, messageContent))
session.contentUrlResolver().resolveFullSize(messageContent.url)?.let { url ->
add(EventSharedAction.Share(url))
}
}
// TODO
} }
if (timelineEvent.root.sendState == SendState.SENT) { if (timelineEvent.root.sendState == SendState.SENT) {
@ -374,7 +369,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
return when (msgType) { return when (msgType) {
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_VIDEO -> true MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_FILE -> true
else -> false else -> false
} }
} }

View File

@ -222,7 +222,7 @@ class MessageItemFactory @Inject constructor(
referenceId = informationData.eventId, referenceId = informationData.eventId,
informationData = informationData, informationData = informationData,
avatarRenderer = attributes.avatarRenderer, avatarRenderer = attributes.avatarRenderer,
colorProvider = attributes.colorProvider, messageColorProvider = attributes.messageColorProvider,
itemLongClickListener = attributes.itemLongClickListener, itemLongClickListener = attributes.itemLongClickListener,
itemClickListener = attributes.itemClickListener, itemClickListener = attributes.itemClickListener,
reactionPillCallback = attributes.reactionPillCallback, reactionPillCallback = attributes.reactionPillCallback,

View File

@ -44,7 +44,8 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
itemLongClickListener = View.OnLongClickListener { view -> itemLongClickListener = View.OnLongClickListener { view ->
callback?.onEventLongClicked(informationData, null, view) ?: false callback?.onEventLongClicked(informationData, null, view) ?: false
}, },
readReceiptsCallback = callback readReceiptsCallback = callback,
avatarClickListener = { callback?.onAvatarClicked(informationData) }
) )
return NoticeItem_() return NoticeItem_()
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)

View File

@ -27,8 +27,8 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.session.room.VerificationState import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController 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.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
@ -43,7 +43,7 @@ import javax.inject.Inject
* several checks are made to see if this conclusion is attached to a known request * several checks are made to see if this conclusion is attached to a known request
*/ */
class VerificationItemFactory @Inject constructor( class VerificationItemFactory @Inject constructor(
private val colorProvider: ColorProvider, private val messageColorProvider: MessageColorProvider,
private val messageInformationDataFactory: MessageInformationDataFactory, private val messageInformationDataFactory: MessageInformationDataFactory,
private val messageItemAttributesFactory: MessageItemAttributesFactory, private val messageItemAttributesFactory: MessageItemAttributesFactory,
private val avatarSizeProvider: AvatarSizeProvider, private val avatarSizeProvider: AvatarSizeProvider,
@ -97,7 +97,7 @@ class VerificationItemFactory @Inject constructor(
isPositive = false, isPositive = false,
informationData = informationData, informationData = informationData,
avatarRenderer = attributes.avatarRenderer, avatarRenderer = attributes.avatarRenderer,
colorProvider = colorProvider, messageColorProvider = messageColorProvider,
emojiTypeFace = attributes.emojiTypeFace, emojiTypeFace = attributes.emojiTypeFace,
itemClickListener = attributes.itemClickListener, itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener, itemLongClickListener = attributes.itemLongClickListener,
@ -130,7 +130,7 @@ class VerificationItemFactory @Inject constructor(
isPositive = true, isPositive = true,
informationData = informationData, informationData = informationData,
avatarRenderer = attributes.avatarRenderer, avatarRenderer = attributes.avatarRenderer,
colorProvider = colorProvider, messageColorProvider = messageColorProvider,
emojiTypeFace = attributes.emojiTypeFace, emojiTypeFace = attributes.emojiTypeFace,
itemClickListener = attributes.itemClickListener, itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener, itemLongClickListener = attributes.itemLongClickListener,

View File

@ -27,14 +27,13 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenScope import im.vector.riotx.core.di.ScreenScope
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.TextUtils import im.vector.riotx.core.utils.TextUtils
import im.vector.riotx.features.ui.getMessageTextColor import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import javax.inject.Inject import javax.inject.Inject
@ScreenScope @ScreenScope
class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val colorProvider: ColorProvider, private val messageColorProvider: MessageColorProvider,
private val errorFormatter: ErrorFormatter) { private val errorFormatter: ErrorFormatter) {
private val updateListeners = mutableMapOf<String, ContentUploadStateTracker.UpdateListener>() private val updateListeners = mutableMapOf<String, ContentUploadStateTracker.UpdateListener>()
@ -44,7 +43,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
progressLayout: ViewGroup) { progressLayout: ViewGroup) {
activeSessionHolder.getSafeActiveSession()?.also { session -> activeSessionHolder.getSafeActiveSession()?.also { session ->
val uploadStateTracker = session.contentUploadProgressTracker() val uploadStateTracker = session.contentUploadProgressTracker()
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter) val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, messageColorProvider, errorFormatter)
updateListeners[eventId] = updateListener updateListeners[eventId] = updateListener
uploadStateTracker.track(eventId, updateListener) uploadStateTracker.track(eventId, updateListener)
} }
@ -68,7 +67,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
private val isLocalFile: Boolean, private val isLocalFile: Boolean,
private val colorProvider: ColorProvider, private val messageColorProvider: MessageColorProvider,
private val errorFormatter: ErrorFormatter) : ContentUploadStateTracker.UpdateListener { private val errorFormatter: ErrorFormatter) : ContentUploadStateTracker.UpdateListener {
override fun onUpdate(state: ContentUploadStateTracker.State) { override fun onUpdate(state: ContentUploadStateTracker.State) {
@ -92,7 +91,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
progressBar?.isIndeterminate = true progressBar?.isIndeterminate = true
progressBar?.progress = 0 progressBar?.progress = 0
progressTextView?.text = progressLayout.context.getString(R.string.send_file_step_idle) progressTextView?.text = progressLayout.context.getString(R.string.send_file_step_idle)
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNSENT)) progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.UNSENT))
} else { } else {
progressLayout.isVisible = false progressLayout.isVisible = false
} }
@ -120,7 +119,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView) val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isIndeterminate = true progressBar?.isIndeterminate = true
progressTextView?.text = progressLayout.context.getString(resId) progressTextView?.text = progressLayout.context.getString(resId)
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.ENCRYPTING)) progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.ENCRYPTING))
} }
private fun doHandleProgress(resId: Int, current: Long, total: Long) { private fun doHandleProgress(resId: Int, current: Long, total: Long) {
@ -134,7 +133,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
progressTextView?.text = progressLayout.context.getString(resId, progressTextView?.text = progressLayout.context.getString(resId,
TextUtils.formatFileSize(progressLayout.context, current, true), TextUtils.formatFileSize(progressLayout.context, current, true),
TextUtils.formatFileSize(progressLayout.context, total, true)) TextUtils.formatFileSize(progressLayout.context, total, true))
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.SENDING)) progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.SENDING))
} }
private fun handleFailure(state: ContentUploadStateTracker.State.Failure) { private fun handleFailure(state: ContentUploadStateTracker.State.Failure) {
@ -143,7 +142,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView) val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isVisible = false progressBar?.isVisible = false
progressTextView?.text = errorFormatter.toHumanReadable(state.throwable) progressTextView?.text = errorFormatter.toHumanReadable(state.throwable)
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNDELIVERED)) progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.UNDELIVERED))
} }
private fun handleSuccess() { private fun handleSuccess() {

View File

@ -20,9 +20,9 @@ package im.vector.riotx.features.home.room.detail.timeline.helper
import android.view.View import android.view.View
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
@ -30,7 +30,7 @@ import javax.inject.Inject
class MessageItemAttributesFactory @Inject constructor( class MessageItemAttributesFactory @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider, private val messageColorProvider: MessageColorProvider,
private val avatarSizeProvider: AvatarSizeProvider, private val avatarSizeProvider: AvatarSizeProvider,
private val emojiCompatFontProvider: EmojiCompatFontProvider) { private val emojiCompatFontProvider: EmojiCompatFontProvider) {
@ -41,7 +41,7 @@ class MessageItemAttributesFactory @Inject constructor(
avatarSize = avatarSizeProvider.avatarSize, avatarSize = avatarSizeProvider.avatarSize,
informationData = informationData, informationData = informationData,
avatarRenderer = avatarRenderer, avatarRenderer = avatarRenderer,
colorProvider = colorProvider, messageColorProvider = messageColorProvider,
itemLongClickListener = View.OnLongClickListener { view -> itemLongClickListener = View.OnLongClickListener { view ->
callback?.onEventLongClicked(informationData, messageContent, view) ?: false callback?.onEventLongClicked(informationData, messageContent, view) ?: false
}, },

View File

@ -24,12 +24,11 @@ import androidx.annotation.IdRes
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.reactions.widget.ReactionButton import im.vector.riotx.features.reactions.widget.ReactionButton
import im.vector.riotx.features.ui.getMessageTextColor
/** /**
* Base timeline item with reactions and read receipts. * Base timeline item with reactions and read receipts.
@ -105,7 +104,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) { protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) {
root.isClickable = baseAttributes.informationData.sendState.isSent() root.isClickable = baseAttributes.informationData.sendState.isSent()
val state = if (baseAttributes.informationData.hasPendingEdits) SendState.UNSENT else baseAttributes.informationData.sendState val state = if (baseAttributes.informationData.hasPendingEdits) SendState.UNSENT else baseAttributes.informationData.sendState
textView?.setTextColor(baseAttributes.colorProvider.getMessageTextColor(state)) textView?.setTextColor(baseAttributes.messageColorProvider.getMessageTextColor(state))
failureIndicator?.isVisible = baseAttributes.informationData.sendState.hasFailed() failureIndicator?.isVisible = baseAttributes.informationData.sendState.hasFailed()
} }
@ -120,7 +119,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
// val avatarSize: Int, // val avatarSize: Int,
val informationData: MessageInformationData val informationData: MessageInformationData
val avatarRenderer: AvatarRenderer val avatarRenderer: AvatarRenderer
val colorProvider: ColorProvider val messageColorProvider: MessageColorProvider
val itemLongClickListener: View.OnLongClickListener? val itemLongClickListener: View.OnLongClickListener?
val itemClickListener: View.OnClickListener? val itemClickListener: View.OnClickListener?
// val memberClickListener: View.OnClickListener? // val memberClickListener: View.OnClickListener?

View File

@ -23,9 +23,9 @@ import android.widget.TextView
import androidx.annotation.IdRes import androidx.annotation.IdRes
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
/** /**
@ -88,7 +88,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
val avatarSize: Int, val avatarSize: Int,
override val informationData: MessageInformationData, override val informationData: MessageInformationData,
override val avatarRenderer: AvatarRenderer, override val avatarRenderer: AvatarRenderer,
override val colorProvider: ColorProvider, override val messageColorProvider: MessageColorProvider,
override val itemLongClickListener: View.OnLongClickListener? = null, override val itemLongClickListener: View.OnLongClickListener? = null,
override val itemClickListener: View.OnClickListener? = null, override val itemClickListener: View.OnClickListener? = null,
val memberClickListener: View.OnClickListener? = null, val memberClickListener: View.OnClickListener? = null,

View File

@ -22,6 +22,8 @@ import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.ClickListener
import im.vector.riotx.core.epoxy.onClick
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer 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.TimelineEventController
@ -42,6 +44,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.view.setOnLongClickListener(attributes.itemLongClickListener) holder.view.setOnLongClickListener(attributes.itemLongClickListener)
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
holder.avatarImageView.onClick(attributes.avatarClickListener)
} }
override fun getEventIds(): List<String> { override fun getEventIds(): List<String> {
@ -60,7 +63,8 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
val informationData: MessageInformationData, val informationData: MessageInformationData,
val noticeText: CharSequence, val noticeText: CharSequence,
val itemLongClickListener: View.OnLongClickListener? = null, val itemLongClickListener: View.OnLongClickListener? = null,
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
val avatarClickListener: ClickListener? = null
) )
companion object { companion object {

View File

@ -73,11 +73,11 @@ class PollResultLineView @JvmOverloads constructor(
orientation = HORIZONTAL orientation = HORIZONTAL
ButterKnife.bind(this) ButterKnife.bind(this)
val typedArray = context.obtainStyledAttributes(attrs, val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PollResultLineView, 0, 0)
R.styleable.PollResultLineView, 0, 0)
label = typedArray.getString(R.styleable.PollResultLineView_optionName) ?: "" label = typedArray.getString(R.styleable.PollResultLineView_optionName) ?: ""
percent = typedArray.getString(R.styleable.PollResultLineView_optionCount) ?: "" percent = typedArray.getString(R.styleable.PollResultLineView_optionCount) ?: ""
optionSelected = typedArray.getBoolean(R.styleable.PollResultLineView_optionSelected, false) optionSelected = typedArray.getBoolean(R.styleable.PollResultLineView_optionSelected, false)
isWinner = typedArray.getBoolean(R.styleable.PollResultLineView_optionIsWinner, false) isWinner = typedArray.getBoolean(R.styleable.PollResultLineView_optionIsWinner, false)
typedArray.recycle()
} }
} }

View File

@ -26,8 +26,8 @@ import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
@ -80,7 +80,7 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
val isPositive: Boolean, val isPositive: Boolean,
override val informationData: MessageInformationData, override val informationData: MessageInformationData,
override val avatarRenderer: AvatarRenderer, override val avatarRenderer: AvatarRenderer,
override val colorProvider: ColorProvider, override val messageColorProvider: MessageColorProvider,
override val itemLongClickListener: View.OnLongClickListener? = null, override val itemLongClickListener: View.OnLongClickListener? = null,
override val itemClickListener: View.OnClickListener? = null, override val itemClickListener: View.OnClickListener? = null,
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,

View File

@ -32,10 +32,10 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationServ
import im.vector.matrix.android.internal.session.room.VerificationState import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.RoomDetailAction import im.vector.riotx.features.home.room.detail.RoomDetailAction
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
@ -166,7 +166,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
// val avatarSize: Int, // val avatarSize: Int,
override val informationData: MessageInformationData, override val informationData: MessageInformationData,
override val avatarRenderer: AvatarRenderer, override val avatarRenderer: AvatarRenderer,
override val colorProvider: ColorProvider, override val messageColorProvider: MessageColorProvider,
override val itemLongClickListener: View.OnLongClickListener? = null, override val itemLongClickListener: View.OnLongClickListener? = null,
override val itemClickListener: View.OnClickListener? = null, override val itemClickListener: View.OnClickListener? = null,
// val memberClickListener: View.OnClickListener? = null, // val memberClickListener: View.OnClickListener? = null,

View File

@ -89,7 +89,7 @@ class LoginCaptchaFragment @Inject constructor(
} }
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
Timber.d("## onReceivedSslError() : " + error.certificate) Timber.d("## onReceivedSslError() : ${error.certificate}")
if (!isAdded) { if (!isAdded) {
return return

View File

@ -209,7 +209,14 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
} else { } else {
// Trick to display the error without text. // Trick to display the error without text.
loginFieldTil.error = " " loginFieldTil.error = " "
passwordFieldTil.error = errorFormatter.toHumanReadable(state.asyncLoginAction.error) if (error is Failure.ServerError
&& error.error.code == MatrixError.M_FORBIDDEN
&& error.error.message == "Invalid password"
&& spaceInPassword()) {
passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password)
} else {
passwordFieldTil.error = errorFormatter.toHumanReadable(error)
}
} }
} }
// Success is handled by the LoginActivity // Success is handled by the LoginActivity
@ -226,4 +233,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
is Success -> Unit is Success -> Unit
} }
} }
/**
* Detect if password ends or starts with spaces
*/
private fun spaceInPassword() = passwordField.text.toString().let { it.trim() != it }
} }

View File

@ -127,6 +127,15 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
GlideApp GlideApp
.with(imageView) .with(imageView)
.load(resolvedUrl) .load(resolvedUrl)
.apply {
if (mode == Mode.THUMBNAIL) {
error(
GlideApp
.with(imageView)
.load(contentUrlResolver.resolveFullSize(data.url))
)
}
}
} }
} }

View File

@ -24,7 +24,6 @@ import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R import im.vector.riotx.R
@ -35,6 +34,7 @@ import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.debug.DebugMenuActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity
@ -56,7 +56,8 @@ import javax.inject.Singleton
@Singleton @Singleton
class DefaultNavigator @Inject constructor( class DefaultNavigator @Inject constructor(
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
) : Navigator { ) : Navigator {
override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) { override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) {
@ -85,9 +86,10 @@ class DefaultNavigator @Inject constructor(
override fun requestSessionVerification(context: Context) { override fun requestSessionVerification(context: Context) {
val session = sessionHolder.getSafeActiveSession() ?: return val session = sessionHolder.getSafeActiveSession() ?: return
val pr = session.cryptoService().verificationService().requestKeyVerification( val pr = session.cryptoService().verificationService().requestKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), supportedVerificationMethodsProvider.provide(),
session.myUserId, session.myUserId,
session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId }) session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId }
)
if (context is VectorBaseActivity) { if (context is VectorBaseActivity) {
VerificationBottomSheet.withArgs( VerificationBottomSheet.withArgs(
roomId = null, roomId = null,

View File

@ -99,9 +99,9 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
val pw = PrintWriter(sw, true) val pw = PrintWriter(sw, true)
throwable.printStackTrace(pw) throwable.printStackTrace(pw)
b.append(sw.buffer.toString()) b.append(sw.buffer.toString())
Timber.e("FATAL EXCEPTION " + b.toString())
val bugDescription = b.toString() val bugDescription = b.toString()
Timber.e("FATAL EXCEPTION $bugDescription")
bugReporter.saveCrashReport(context, bugDescription) bugReporter.saveCrashReport(context, bugDescription)

View File

@ -414,7 +414,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
} }
Timber.v("## getNotificationRingTone() returns " + uri!!) Timber.v("## getNotificationRingTone() returns $uri")
return uri return uri
} }

View File

@ -40,7 +40,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
data class DevicesViewState( data class DevicesViewState(
val myDeviceId: String = "", val myDeviceId: String = "",
@ -50,8 +50,10 @@ data class DevicesViewState(
val request: Async<Unit> = Uninitialized val request: Async<Unit> = Uninitialized
) : MvRxState ) : MvRxState
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState, class DevicesViewModel @AssistedInject constructor(
private val session: Session) @Assisted initialState: DevicesViewState,
private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider)
: VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener { : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
@AssistedInject.Factory @AssistedInject.Factory
@ -172,7 +174,9 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
} }
private fun handleVerify(action: DevicesAction.VerifyMyDevice) { private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
val txID = session.cryptoService().verificationService().requestKeyVerification(supportedVerificationMethods, session.myUserId, listOf(action.deviceId)) val txID = session.cryptoService()
.verificationService()
.requestKeyVerification(supportedVerificationMethodsProvider.provide(), session.myUserId, listOf(action.deviceId))
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice( _viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
session.myUserId, session.myUserId,
txID.transactionId txID.transactionId

View File

@ -28,7 +28,6 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.R import im.vector.riotx.R
@ -78,7 +77,7 @@ class IncomingShareFragment @Inject constructor(
val intent = vectorBaseActivity.intent val intent = vectorBaseActivity.intent
val isShareManaged = when (intent?.action) { val isShareManaged = when (intent?.action) {
Intent.ACTION_SEND -> { Intent.ACTION_SEND -> {
var isShareManaged = attachmentsHelper.handleShareIntent(IntentUtils.getPickerIntentForSharing(intent)) var isShareManaged = attachmentsHelper.handleShareIntent(intent)
if (!isShareManaged) { if (!isShareManaged) {
isShareManaged = handleTextShare(intent) isShareManaged = handleTextShare(intent)
} }

View File

@ -1,37 +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.features.ui
import androidx.annotation.ColorInt
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
@ColorInt
fun ColorProvider.getMessageTextColor(sendState: SendState): Int {
return when (sendState) {
// SendStates, in the classical order they will occur
SendState.UNKNOWN,
SendState.UNSENT -> getColorFromAttribute(R.attr.vctr_sending_message_text_color)
SendState.ENCRYPTING -> getColorFromAttribute(R.attr.vctr_encrypting_message_text_color)
SendState.SENDING -> getColorFromAttribute(R.attr.vctr_sending_message_text_color)
SendState.SENT,
SendState.SYNCED -> getColorFromAttribute(R.attr.vctr_message_text_color)
SendState.UNDELIVERED,
SendState.FAILED_UNKNOWN_DEVICES -> getColorFromAttribute(R.attr.vctr_unsent_message_text_color)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Some files were not shown because too many files have changed in this diff Show More