Merge branch 'develop' into feature/notif
@ -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"
|
3
.idea/codeStyles/Project.xml
generated
@ -1,9 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
|
@ -49,12 +49,12 @@ script:
|
||||
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||
# Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --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)
|
||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||
# 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
|
||||
# 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
|
||||
|
13
CHANGES.md
@ -5,10 +5,17 @@ Features ✨:
|
||||
-
|
||||
|
||||
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
|
||||
|
||||
Bugfix 🐛:
|
||||
- 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 🗣:
|
||||
-
|
||||
@ -17,10 +24,12 @@ SDK API changes ⚠️:
|
||||
- PushRuleService.getPushRules() now returns a RuleSet. Use getAllRules() on this object to get all the rules.
|
||||
|
||||
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:
|
||||
-
|
||||
- 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)
|
||||
===================================================
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
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
@ -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
|
||||
```
|
@ -126,7 +126,7 @@ dependencies {
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
@ -31,7 +31,7 @@ data class ContentAttachmentData(
|
||||
val name: String? = null,
|
||||
val queryUri: String,
|
||||
val path: String,
|
||||
val mimeType: String?,
|
||||
private val mimeType: String?,
|
||||
val type: Type
|
||||
) : Parcelable {
|
||||
|
||||
@ -41,4 +41,6 @@ data class ContentAttachmentData(
|
||||
AUDIO,
|
||||
VIDEO
|
||||
}
|
||||
|
||||
fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
|
||||
}
|
||||
|
@ -34,7 +34,11 @@ interface FileService {
|
||||
/**
|
||||
* Download file in cache
|
||||
*/
|
||||
FOR_INTERNAL_USE
|
||||
FOR_INTERNAL_USE,
|
||||
/**
|
||||
* Download file in file provider path
|
||||
*/
|
||||
FOR_EXTERNAL_SHARE
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*/
|
||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||
) : MessageEncryptedContent
|
||||
) : MessageWithAttachmentContent
|
||||
|
@ -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.
|
||||
*/
|
||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||
) : MessageEncryptedContent {
|
||||
) : MessageWithAttachmentContent {
|
||||
|
||||
fun getMimeType(): String {
|
||||
// Mimetype default to plain text, should not be used
|
||||
|
@ -20,6 +20,6 @@ package im.vector.matrix.android.api.session.room.model.message
|
||||
/**
|
||||
* A content with image information
|
||||
*/
|
||||
interface MessageImageInfoContent : MessageEncryptedContent {
|
||||
interface MessageImageInfoContent : MessageWithAttachmentContent {
|
||||
val info: ImageInfo?
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||
) : MessageEncryptedContent
|
||||
) : MessageWithAttachmentContent
|
||||
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@ -36,4 +36,4 @@ interface MessageEncryptedContent : MessageContent {
|
||||
/**
|
||||
* Get the url of the encrypted file or of the file
|
||||
*/
|
||||
fun MessageEncryptedContent.getFileUrl() = encryptedFileInfo?.url ?: url
|
||||
fun MessageWithAttachmentContent.getFileUrl() = encryptedFileInfo?.url ?: url
|
@ -25,3 +25,7 @@ annotation class SessionFilesDirectory
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionCacheDirectory
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CacheDirectory
|
||||
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
|
||||
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class])
|
||||
@MatrixScope
|
||||
@ -52,6 +53,9 @@ internal interface MatrixComponent {
|
||||
|
||||
fun resources(): Resources
|
||||
|
||||
@CacheDirectory
|
||||
fun cacheDir(): File
|
||||
|
||||
fun olmManager(): OlmManager
|
||||
|
||||
fun taskExecutor(): TaskExecutor
|
||||
|
@ -26,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@Module
|
||||
@ -49,6 +50,13 @@ internal object MatrixModule {
|
||||
return context.resources
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@CacheDirectory
|
||||
fun providesCacheDir(context: Context): File {
|
||||
return context.cacheDir
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@MatrixScope
|
||||
|
@ -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.internal.crypto.attachments.ElementToDecrypt
|
||||
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.Unauthenticated
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
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.writeToFile
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -42,8 +42,10 @@ import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultFileService @Inject constructor(
|
||||
@SessionCacheDirectory
|
||||
@CacheDirectory
|
||||
private val cacheDirectory: File,
|
||||
@SessionCacheDirectory
|
||||
private val sessionCacheDirectory: File,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
@Unauthenticated
|
||||
private val okHttpClient: OkHttpClient,
|
||||
@ -62,60 +64,50 @@ internal class DefaultFileService @Inject constructor(
|
||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
Try {
|
||||
val folder = getFolder(downloadMode, id)
|
||||
|
||||
val folder = File(sessionCacheDirectory, "MF")
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
}
|
||||
File(folder, fileName)
|
||||
}.flatMap { destFile ->
|
||||
if (!destFile.exists() || downloadMode == FileService.DownloadMode.TO_EXPORT) {
|
||||
Try {
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
||||
if (!destFile.exists()) {
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(resolvedUrl)
|
||||
.build()
|
||||
val request = Request.Builder()
|
||||
.url(resolvedUrl)
|
||||
.build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
var inputStream = response.body?.byteStream()
|
||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||
if (!response.isSuccessful
|
||||
|| inputStream == null) {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
?: throw IllegalStateException("Decryption error")
|
||||
}
|
||||
|
||||
writeToFile(inputStream, destFile)
|
||||
destFile
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
var inputStream = response.body?.byteStream()
|
||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||
if (!response.isSuccessful || inputStream == null) {
|
||||
return@flatMap Try.Failure(IOException())
|
||||
}
|
||||
} else {
|
||||
Try.just(destFile)
|
||||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
?: return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
||||
}
|
||||
|
||||
writeToFile(inputStream, destFile)
|
||||
}
|
||||
|
||||
Try.just(copyFile(destFile, downloadMode))
|
||||
}
|
||||
}
|
||||
.foldToCallback(callback)
|
||||
}.toCancelable()
|
||||
}
|
||||
|
||||
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File {
|
||||
private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File {
|
||||
return when (downloadMode) {
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
||||
// Create dir tree (MF stands for Matrix File):
|
||||
// <cache>/<sessionId>/MF/<md5(id)>/
|
||||
val tmpFolderSession = File(cacheDirectory, "MF")
|
||||
File(tmpFolderSession, id.md5())
|
||||
}
|
||||
FileService.DownloadMode.TO_EXPORT -> {
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
FileService.DownloadMode.TO_EXPORT ->
|
||||
file.copyTo(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), file.name), true)
|
||||
FileService.DownloadMode.FOR_EXTERNAL_SHARE ->
|
||||
file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true)
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE ->
|
||||
file
|
||||
}
|
||||
.also { folder ->
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
@ -33,7 +34,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||
val listeners = listeners.getOrPut(key) { ArrayList() }
|
||||
listeners.add(updateListener)
|
||||
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) {
|
||||
@ -79,7 +86,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
|
||||
states[key] = state
|
||||
mainHandler.post {
|
||||
listeners[key]?.forEach { it.onUpdate(state) }
|
||||
listeners[key]?.forEach {
|
||||
try {
|
||||
it.onUpdate(state)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## ContentUploadStateTracker.onUpdate() failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||
override val sessionId: String,
|
||||
val events: List<Event>,
|
||||
val attachment: ContentAttachmentData,
|
||||
val isRoomEncrypted: Boolean,
|
||||
val isEncrypted: Boolean,
|
||||
val compressBeforeSending: Boolean,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
@ -90,9 +90,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||
Timber.e(e)
|
||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
))
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
.let { originalFile ->
|
||||
@ -136,7 +138,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||
}
|
||||
|
||||
try {
|
||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt thumbnail")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
||||
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
|
||||
|
||||
return try {
|
||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt file")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
|
||||
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
|
||||
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
||||
|
||||
fileUploader
|
||||
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
|
||||
} else {
|
||||
fileUploader
|
||||
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
|
||||
.uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||
}
|
||||
|
||||
handleSuccess(params,
|
||||
@ -226,7 +228,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||
msgType = MessageType.MSGTYPE_IMAGE,
|
||||
body = attachment.name ?: "image",
|
||||
info = ImageInfo(
|
||||
mimeType = attachment.mimeType,
|
||||
mimeType = attachment.getSafeMimeType(),
|
||||
width = width?.toInt() ?: 0,
|
||||
height = height?.toInt() ?: 0,
|
||||
size = attachment.size.toInt()
|
||||
@ -293,7 +293,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||
msgType = MessageType.MSGTYPE_VIDEO,
|
||||
body = attachment.name ?: "video",
|
||||
videoInfo = VideoInfo(
|
||||
mimeType = attachment.mimeType,
|
||||
mimeType = attachment.getSafeMimeType(),
|
||||
width = width,
|
||||
height = height,
|
||||
size = attachment.size,
|
||||
@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||
msgType = MessageType.MSGTYPE_AUDIO,
|
||||
body = attachment.name ?: "audio",
|
||||
audioInfo = AudioInfo(
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
size = attachment.size
|
||||
),
|
||||
url = attachment.path
|
||||
@ -325,7 +325,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||
msgType = MessageType.MSGTYPE_FILE,
|
||||
body = attachment.name ?: "file",
|
||||
info = FileInfo(
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() }
|
||||
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }
|
||||
?: "application/octet-stream",
|
||||
size = attachment.size
|
||||
),
|
||||
|
Before Width: | Height: | Size: 510 B |
@ -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>
|
@ -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>
|
@ -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>
|
@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
|
||||
</resources>
|
@ -296,7 +296,7 @@ dependencies {
|
||||
implementation 'com.airbnb.android:mvrx:1.3.0'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||
|
@ -6,9 +6,15 @@
|
||||
<issue id="MissingTranslation" severity="warning" />
|
||||
<issue id="TypographyEllipsis" severity="error" />
|
||||
<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 -->
|
||||
<issue id="ButtonOrder" severity="error" />
|
||||
<issue id="TextFields" severity="error" />
|
||||
|
||||
<!-- Layout -->
|
||||
<issue id="UnknownIdInLayout" severity="error" />
|
||||
@ -19,6 +25,7 @@
|
||||
<issue id="InefficientWeight" severity="error" />
|
||||
<issue id="DisableBaselineAlignment" severity="error" />
|
||||
<issue id="ScrollViewSize" severity="error" />
|
||||
<issue id="NegativeMargin" severity="error" />
|
||||
|
||||
<!-- RTL -->
|
||||
<issue id="RtlEnabled" severity="error" />
|
||||
@ -30,9 +37,21 @@
|
||||
<issue id="SetTextI18n" severity="error" />
|
||||
<issue id="ViewConstructor" severity="error" />
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
<issue id="Recycle" severity="error" />
|
||||
<issue id="KotlinPropertyAccess" severity="error" />
|
||||
|
||||
<!-- Ignore error from HtmlCompressor lib -->
|
||||
<issue id="InvalidPackage">
|
||||
<ignore path="**/htmlcompressor-1.4.jar"/>
|
||||
<ignore path="**/htmlcompressor-1.4.jar" />
|
||||
</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>
|
||||
|
@ -114,7 +114,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||
.setContentText("Content")
|
||||
// No effect because it's a group summary notif
|
||||
.setNumber(33)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
// This provocate the badge issue: no badge for group notification
|
||||
.setGroup("GroupKey")
|
||||
.setGroupSummary(true)
|
||||
@ -147,7 +147,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||
// For shortcut on long press on launcher icon
|
||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
|
||||
.setStyle(messagingStyle1)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
.setGroup("GroupKey")
|
||||
.build()
|
||||
)
|
||||
@ -159,7 +159,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||
.setContentTitle("Title 2")
|
||||
.setContentText("Content 2")
|
||||
.setStyle(messagingStyle2)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
.setGroup("GroupKey")
|
||||
.build()
|
||||
)
|
||||
|
@ -9,6 +9,15 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<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
|
||||
android:name=".VectorApplication"
|
||||
android:allowBackup="false"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -129,7 +129,7 @@ class BadgeFloatingActionButton @JvmOverloads constructor(
|
||||
attrs?.let { initAttrs(attrs) }
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType", "Recycle")
|
||||
@SuppressWarnings("Recycle")
|
||||
private fun initAttrs(attrs: AttributeSet) {
|
||||
context.obtainStyledAttributes(attrs, R.styleable.BadgeFloatingActionButton).use {
|
||||
counterBackgroundColor = it.getColor(R.styleable.BadgeFloatingActionButton_badgeBackgroundColor, 0)
|
||||
|
@ -144,10 +144,10 @@ fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): Strin
|
||||
Timber.e("Cannot use the external storage media to save image")
|
||||
}
|
||||
} catch (uoe: UnsupportedOperationException) {
|
||||
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(uoe, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI.")
|
||||
Timber.e("no SD card? Attempting to insert into device storage.")
|
||||
} 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) {
|
||||
@ -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")
|
||||
}
|
||||
} 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) {
|
||||
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 {
|
||||
Timber.v("trying to take a photo with no predefined uri")
|
||||
}
|
||||
|
@ -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.core.ImagePickerImpl
|
||||
import com.kbeanie.multipicker.core.PickerManager
|
||||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.riotx.core.platform.Restorable
|
||||
@ -176,13 +177,13 @@ class AttachmentsHelper private constructor(private val context: Context,
|
||||
fun handleShareIntent(intent: Intent): Boolean {
|
||||
val type = intent.resolveType(context) ?: return false
|
||||
if (type.startsWith("image")) {
|
||||
imagePicker.submit(intent)
|
||||
imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("video")) {
|
||||
videoPicker.submit(intent)
|
||||
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("audio")) {
|
||||
videoPicker.submit(intent)
|
||||
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
|
||||
filePicker.submit(intent)
|
||||
filePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -23,6 +23,6 @@ import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
*/
|
||||
fun ContentAttachmentData.isEditable(): Boolean {
|
||||
return type == ContentAttachmentData.Type.IMAGE
|
||||
&& mimeType?.startsWith("image/") == true
|
||||
&& mimeType != "image/gif"
|
||||
&& getSafeMimeType()?.startsWith("image/") == true
|
||||
&& getSafeMimeType() != "image/gif"
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler),
|
||||
POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll),
|
||||
SHRUG("/shrug", "<message>", R.string.command_description_shrug),
|
||||
PLAIN("/plain", "<message>", R.string.command_description_plain),
|
||||
// TODO temporary command
|
||||
VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify);
|
||||
|
||||
|
@ -57,6 +57,15 @@ object CommandParser {
|
||||
}
|
||||
|
||||
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 -> {
|
||||
val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()
|
||||
|
||||
|
@ -33,6 +33,7 @@ sealed class ParsedCommand {
|
||||
|
||||
// Valid commands:
|
||||
|
||||
class SendPlainText(val message: CharSequence) : ParsedCommand()
|
||||
class SendEmote(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbow(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbowEmote(val message: CharSequence) : ParsedCommand()
|
||||
|
@ -36,8 +36,8 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
|
||||
// TODO Import mLanguageReceiver From Riot?
|
||||
fun onConfigurationChanged() {
|
||||
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
|
||||
Timber.v("## onConfigurationChanged() : the locale has been updated to " + Locale.getDefault().toString()
|
||||
+ ", restore the expected value " + VectorLocale.applicationLocale.toString())
|
||||
Timber.v("## onConfigurationChanged(): the locale has been updated to ${Locale.getDefault()}")
|
||||
Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}")
|
||||
updateApplicationSettings(VectorLocale.applicationLocale,
|
||||
FontScale.getFontScalePrefValue(context),
|
||||
ThemeUtils.getApplicationTheme(context))
|
||||
|
@ -74,7 +74,7 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel
|
||||
isIndeterminate = true))
|
||||
}
|
||||
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
|
||||
if (step.progress == 0) {
|
||||
sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
|
@ -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
|
||||
)
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -63,9 +63,11 @@ data class VerificationBottomSheetViewState(
|
||||
val isMe: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
|
||||
@Assisted args: VerificationBottomSheet.VerificationArgs,
|
||||
private val session: Session)
|
||||
class VerificationBottomSheetViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VerificationBottomSheetViewState,
|
||||
@Assisted args: VerificationBottomSheet.VerificationArgs,
|
||||
private val session: Session,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider)
|
||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
|
||||
VerificationService.Listener {
|
||||
|
||||
@ -116,9 +118,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||
if (autoReady) {
|
||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(supportedVerificationMethods,
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr!!.otherUserId,
|
||||
pr.transactionId ?: "")
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +177,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||
session
|
||||
.cryptoService()
|
||||
.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
|
||||
.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)
|
||||
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -294,8 +303,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage))
|
||||
}
|
||||
|
||||
Unit
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
@ -362,9 +369,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||
// 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?
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(supportedVerificationMethods,
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr.otherUserId,
|
||||
pr.transactionId ?: "")
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
// Use this one!
|
||||
|
@ -37,6 +37,7 @@ import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.util.Pair
|
||||
import androidx.core.view.ViewCompat
|
||||
@ -57,17 +58,17 @@ import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
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.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
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.session.Session
|
||||
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.file.FileService
|
||||
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.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.MessageVerificationRequestContent
|
||||
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.timeline.Timeline
|
||||
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.util.MatrixItem
|
||||
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.core.dialogs.withColoredButton
|
||||
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.files.addEntryToDownloadManager
|
||||
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.resources.ColorProvider
|
||||
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
|
||||
@ -1124,6 +1128,23 @@ class RoomDetailFragment @Inject constructor(
|
||||
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) {
|
||||
when (action) {
|
||||
is EventSharedAction.OpenUserProfile -> {
|
||||
@ -1145,32 +1166,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
promptConfirmationToRedactEvent(action)
|
||||
}
|
||||
is EventSharedAction.Share -> {
|
||||
// TODO current data communication is too limited
|
||||
// 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() {}
|
||||
}
|
||||
)
|
||||
onShareActionClicked(action)
|
||||
}
|
||||
is EventSharedAction.ViewEditHistory -> {
|
||||
onEditedDecorationClicked(action.messageInformationData)
|
||||
|
@ -65,7 +65,7 @@ import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
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.timeline.helper.TimelineDisplayableEvents
|
||||
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.atomic.AtomicBoolean
|
||||
|
||||
class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
|
||||
userPreferencesProvider: UserPreferencesProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val session: Session
|
||||
class RoomDetailViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: RoomDetailViewState,
|
||||
userPreferencesProvider: UserPreferencesProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val session: Session,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
@ -340,6 +342,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
||||
_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 -> {
|
||||
handleInviteSlashCommand(slashCommandResult)
|
||||
popDraft()
|
||||
@ -421,7 +429,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
session
|
||||
.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
|
||||
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), slashCommandResult.userId, room.roomId)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
@ -828,7 +836,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
||||
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
|
||||
supportedVerificationMethods,
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
action.otherUserId,
|
||||
room.roomId,
|
||||
action.transactionId)) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorSharedAction
|
||||
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) :
|
||||
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)
|
||||
|
||||
data class Resend(val eventId: String) :
|
||||
|
@ -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.toModel
|
||||
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.MessageImageContent
|
||||
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.MessageVerificationRequestContent
|
||||
@ -260,13 +260,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||
add(EventSharedAction.ViewEditHistory(informationData))
|
||||
}
|
||||
|
||||
if (canShare(msgType)) {
|
||||
if (messageContent is MessageImageContent) {
|
||||
session.contentUrlResolver().resolveFullSize(messageContent.url)?.let { url ->
|
||||
add(EventSharedAction.Share(url))
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
if (canShare(msgType) && messageContent is MessageWithAttachmentContent) {
|
||||
add(EventSharedAction.Share(timelineEvent.eventId, messageContent))
|
||||
}
|
||||
|
||||
if (timelineEvent.root.sendState == SendState.SENT) {
|
||||
@ -374,8 +369,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||
return when (msgType) {
|
||||
MessageType.MSGTYPE_IMAGE,
|
||||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_VIDEO -> true
|
||||
else -> false
|
||||
MessageType.MSGTYPE_VIDEO,
|
||||
MessageType.MSGTYPE_FILE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ class MessageItemFactory @Inject constructor(
|
||||
referenceId = informationData.eventId,
|
||||
informationData = informationData,
|
||||
avatarRenderer = attributes.avatarRenderer,
|
||||
colorProvider = attributes.colorProvider,
|
||||
messageColorProvider = attributes.messageColorProvider,
|
||||
itemLongClickListener = attributes.itemLongClickListener,
|
||||
itemClickListener = attributes.itemClickListener,
|
||||
reactionPillCallback = attributes.reactionPillCallback,
|
||||
|
@ -44,7 +44,8 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
|
||||
itemLongClickListener = View.OnLongClickListener { view ->
|
||||
callback?.onEventLongClicked(informationData, null, view) ?: false
|
||||
},
|
||||
readReceiptsCallback = callback
|
||||
readReceiptsCallback = callback,
|
||||
avatarClickListener = { callback?.onAvatarClicked(informationData) }
|
||||
)
|
||||
return NoticeItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
|
@ -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.internal.session.room.VerificationState
|
||||
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.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.helper.AvatarSizeProvider
|
||||
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
|
||||
*/
|
||||
class VerificationItemFactory @Inject constructor(
|
||||
private val colorProvider: ColorProvider,
|
||||
private val messageColorProvider: MessageColorProvider,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
@ -97,7 +97,7 @@ class VerificationItemFactory @Inject constructor(
|
||||
isPositive = false,
|
||||
informationData = informationData,
|
||||
avatarRenderer = attributes.avatarRenderer,
|
||||
colorProvider = colorProvider,
|
||||
messageColorProvider = messageColorProvider,
|
||||
emojiTypeFace = attributes.emojiTypeFace,
|
||||
itemClickListener = attributes.itemClickListener,
|
||||
itemLongClickListener = attributes.itemLongClickListener,
|
||||
@ -130,7 +130,7 @@ class VerificationItemFactory @Inject constructor(
|
||||
isPositive = true,
|
||||
informationData = informationData,
|
||||
avatarRenderer = attributes.avatarRenderer,
|
||||
colorProvider = colorProvider,
|
||||
messageColorProvider = messageColorProvider,
|
||||
emojiTypeFace = attributes.emojiTypeFace,
|
||||
itemClickListener = attributes.itemClickListener,
|
||||
itemLongClickListener = attributes.itemLongClickListener,
|
||||
|
@ -27,14 +27,13 @@ import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.di.ScreenScope
|
||||
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.features.ui.getMessageTextColor
|
||||
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@ScreenScope
|
||||
class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val messageColorProvider: MessageColorProvider,
|
||||
private val errorFormatter: ErrorFormatter) {
|
||||
|
||||
private val updateListeners = mutableMapOf<String, ContentUploadStateTracker.UpdateListener>()
|
||||
@ -44,7 +43,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
|
||||
progressLayout: ViewGroup) {
|
||||
activeSessionHolder.getSafeActiveSession()?.also { session ->
|
||||
val uploadStateTracker = session.contentUploadProgressTracker()
|
||||
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter)
|
||||
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, messageColorProvider, errorFormatter)
|
||||
updateListeners[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 val isLocalFile: Boolean,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val messageColorProvider: MessageColorProvider,
|
||||
private val errorFormatter: ErrorFormatter) : ContentUploadStateTracker.UpdateListener {
|
||||
|
||||
override fun onUpdate(state: ContentUploadStateTracker.State) {
|
||||
@ -92,7 +91,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
||||
progressBar?.isIndeterminate = true
|
||||
progressBar?.progress = 0
|
||||
progressTextView?.text = progressLayout.context.getString(R.string.send_file_step_idle)
|
||||
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNSENT))
|
||||
progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.UNSENT))
|
||||
} else {
|
||||
progressLayout.isVisible = false
|
||||
}
|
||||
@ -120,7 +119,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
||||
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
|
||||
progressBar?.isIndeterminate = true
|
||||
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) {
|
||||
@ -134,7 +133,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
||||
progressTextView?.text = progressLayout.context.getString(resId,
|
||||
TextUtils.formatFileSize(progressLayout.context, current, 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) {
|
||||
@ -143,7 +142,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
||||
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
|
||||
progressBar?.isVisible = false
|
||||
progressTextView?.text = errorFormatter.toHumanReadable(state.throwable)
|
||||
progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNDELIVERED))
|
||||
progressTextView?.setTextColor(messageColorProvider.getMessageTextColor(SendState.UNDELIVERED))
|
||||
}
|
||||
|
||||
private fun handleSuccess() {
|
||||
|
@ -20,9 +20,9 @@ package im.vector.riotx.features.home.room.detail.timeline.helper
|
||||
import android.view.View
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.riotx.EmojiCompatFontProvider
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||
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.item.AbsMessageItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
@ -30,7 +30,7 @@ import javax.inject.Inject
|
||||
|
||||
class MessageItemAttributesFactory @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val messageColorProvider: MessageColorProvider,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
|
||||
|
||||
@ -41,7 +41,7 @@ class MessageItemAttributesFactory @Inject constructor(
|
||||
avatarSize = avatarSizeProvider.avatarSize,
|
||||
informationData = informationData,
|
||||
avatarRenderer = avatarRenderer,
|
||||
colorProvider = colorProvider,
|
||||
messageColorProvider = messageColorProvider,
|
||||
itemLongClickListener = View.OnLongClickListener { view ->
|
||||
callback?.onEventLongClicked(informationData, messageContent, view) ?: false
|
||||
},
|
||||
|
@ -24,12 +24,11 @@ import androidx.annotation.IdRes
|
||||
import androidx.core.view.isVisible
|
||||
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.core.utils.DebouncedClickListener
|
||||
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.reactions.widget.ReactionButton
|
||||
import im.vector.riotx.features.ui.getMessageTextColor
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
root.isClickable = baseAttributes.informationData.sendState.isSent()
|
||||
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()
|
||||
}
|
||||
|
||||
@ -120,7 +119,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
||||
// val avatarSize: Int,
|
||||
val informationData: MessageInformationData
|
||||
val avatarRenderer: AvatarRenderer
|
||||
val colorProvider: ColorProvider
|
||||
val messageColorProvider: MessageColorProvider
|
||||
val itemLongClickListener: View.OnLongClickListener?
|
||||
val itemClickListener: View.OnClickListener?
|
||||
// val memberClickListener: View.OnClickListener?
|
||||
|
@ -23,9 +23,9 @@ import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||
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
|
||||
|
||||
/**
|
||||
@ -88,7 +88,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
||||
val avatarSize: Int,
|
||||
override val informationData: MessageInformationData,
|
||||
override val avatarRenderer: AvatarRenderer,
|
||||
override val colorProvider: ColorProvider,
|
||||
override val messageColorProvider: MessageColorProvider,
|
||||
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
override val itemClickListener: View.OnClickListener? = null,
|
||||
val memberClickListener: View.OnClickListener? = null,
|
||||
|
@ -22,6 +22,8 @@ import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
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.features.home.AvatarRenderer
|
||||
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)
|
||||
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
|
||||
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
|
||||
holder.avatarImageView.onClick(attributes.avatarClickListener)
|
||||
}
|
||||
|
||||
override fun getEventIds(): List<String> {
|
||||
@ -60,7 +63,8 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
val informationData: MessageInformationData,
|
||||
val noticeText: CharSequence,
|
||||
val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||
val avatarClickListener: ClickListener? = null
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -73,11 +73,11 @@ class PollResultLineView @JvmOverloads constructor(
|
||||
orientation = HORIZONTAL
|
||||
ButterKnife.bind(this)
|
||||
|
||||
val typedArray = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.PollResultLineView, 0, 0)
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PollResultLineView, 0, 0)
|
||||
label = typedArray.getString(R.styleable.PollResultLineView_optionName) ?: ""
|
||||
percent = typedArray.getString(R.styleable.PollResultLineView_optionCount) ?: ""
|
||||
optionSelected = typedArray.getBoolean(R.styleable.PollResultLineView_optionSelected, false)
|
||||
isWinner = typedArray.getBoolean(R.styleable.PollResultLineView_optionIsWinner, false)
|
||||
typedArray.recycle()
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
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.room.detail.timeline.MessageColorProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
||||
@ -80,7 +80,7 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
|
||||
val isPositive: Boolean,
|
||||
override val informationData: MessageInformationData,
|
||||
override val avatarRenderer: AvatarRenderer,
|
||||
override val colorProvider: ColorProvider,
|
||||
override val messageColorProvider: MessageColorProvider,
|
||||
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
override val itemClickListener: View.OnClickListener? = null,
|
||||
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||
|
@ -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.riotx.R
|
||||
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.features.home.AvatarRenderer
|
||||
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
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
||||
@ -166,7 +166,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
|
||||
// val avatarSize: Int,
|
||||
override val informationData: MessageInformationData,
|
||||
override val avatarRenderer: AvatarRenderer,
|
||||
override val colorProvider: ColorProvider,
|
||||
override val messageColorProvider: MessageColorProvider,
|
||||
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
override val itemClickListener: View.OnClickListener? = null,
|
||||
// val memberClickListener: View.OnClickListener? = null,
|
||||
|
@ -89,7 +89,7 @@ class LoginCaptchaFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||
Timber.d("## onReceivedSslError() : " + error.certificate)
|
||||
Timber.d("## onReceivedSslError() : ${error.certificate}")
|
||||
|
||||
if (!isAdded) {
|
||||
return
|
||||
|
@ -209,7 +209,14 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
} else {
|
||||
// Trick to display the error without text.
|
||||
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
|
||||
@ -226,4 +233,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
is Success -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if password ends or starts with spaces
|
||||
*/
|
||||
private fun spaceInPassword() = passwordField.text.toString().let { it.trim() != it }
|
||||
}
|
||||
|
@ -127,6 +127,15 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
.load(resolvedUrl)
|
||||
.apply {
|
||||
if (mode == Mode.THUMBNAIL) {
|
||||
error(
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
.load(contentUrlResolver.resolveFullSize(data.url))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@ import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import androidx.core.view.ViewCompat
|
||||
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.util.MatrixItem
|
||||
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.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
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.debug.DebugMenuActivity
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||
@ -56,7 +56,8 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class DefaultNavigator @Inject constructor(
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
||||
) : Navigator {
|
||||
|
||||
override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) {
|
||||
@ -85,9 +86,10 @@ class DefaultNavigator @Inject constructor(
|
||||
override fun requestSessionVerification(context: Context) {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||
val pr = session.cryptoService().verificationService().requestKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
session.myUserId,
|
||||
session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId })
|
||||
session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId }
|
||||
)
|
||||
if (context is VectorBaseActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
|
@ -99,9 +99,9 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
|
||||
val pw = PrintWriter(sw, true)
|
||||
throwable.printStackTrace(pw)
|
||||
b.append(sw.buffer.toString())
|
||||
Timber.e("FATAL EXCEPTION " + b.toString())
|
||||
|
||||
val bugDescription = b.toString()
|
||||
Timber.e("FATAL EXCEPTION $bugDescription")
|
||||
|
||||
bugReporter.saveCrashReport(context, bugDescription)
|
||||
|
||||
|
@ -414,7 +414,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||
}
|
||||
|
||||
Timber.v("## getNotificationRingTone() returns " + uri!!)
|
||||
Timber.v("## getNotificationRingTone() returns $uri")
|
||||
return uri
|
||||
}
|
||||
|
||||
|
@ -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.rx.rx
|
||||
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(
|
||||
val myDeviceId: String = "",
|
||||
@ -50,8 +50,10 @@ data class DevicesViewState(
|
||||
val request: Async<Unit> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
|
||||
private val session: Session)
|
||||
class DevicesViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: DevicesViewState,
|
||||
private val session: Session,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider)
|
||||
: VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
|
||||
|
||||
@AssistedInject.Factory
|
||||
@ -172,7 +174,9 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
||||
}
|
||||
|
||||
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(
|
||||
session.myUserId,
|
||||
txID.transactionId
|
||||
|
@ -28,7 +28,6 @@ import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
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.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
@ -78,7 +77,7 @@ class IncomingShareFragment @Inject constructor(
|
||||
val intent = vectorBaseActivity.intent
|
||||
val isShareManaged = when (intent?.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
var isShareManaged = attachmentsHelper.handleShareIntent(IntentUtils.getPickerIntentForSharing(intent))
|
||||
var isShareManaged = attachmentsHelper.handleShareIntent(intent)
|
||||
if (!isShareManaged) {
|
||||
isShareManaged = handleTextShare(intent)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 404 B |
Before Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 269 B |
Before Width: | Height: | Size: 572 B |
Before Width: | Height: | Size: 369 B |
Before Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 266 B |
Before Width: | Height: | Size: 177 B |
Before Width: | Height: | Size: 539 B |
Before Width: | Height: | Size: 545 B |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 645 B |
Before Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 377 B |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 530 B |
Before Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 399 B |
Before Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 574 B |
Before Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 509 B |