Merge branch 'develop' into feature/fix_share_image

This commit is contained in:
Onuray Sahin 2020-03-04 16:27:01 +01:00 committed by GitHub
commit 1680cd8d8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 388 additions and 202 deletions

View file

@ -81,7 +81,7 @@ steps:
- label: "ktlint"
command:
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.36.0/ktlint && chmod a+x ktlint"
- "./ktlint --android --experimental -v"
plugins:
- docker#v3.1.0:

View file

@ -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>

View file

@ -6,6 +6,9 @@ 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.
Bugfix 🐛:
- Fix crash on attachment preview screen (#1088)
@ -19,10 +22,11 @@ SDK API changes ⚠️:
-
Build 🧱:
-
- Upgrade ktlint to version 0.36.0
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)
===================================================

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.
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
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

@ -6,6 +6,11 @@
<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" />
@ -19,6 +24,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 +36,14 @@
<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" />
</lint>

View file

@ -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"

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) }
}
@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)

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),
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);

View file

@ -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()

View file

@ -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()

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
) : 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!

View file

@ -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)) {

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

@ -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,

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.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,

View file

@ -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() {

View file

@ -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
},

View file

@ -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?

View file

@ -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,

View file

@ -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()
}
}

View file

@ -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,

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.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,

View file

@ -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 }
}

View file

@ -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,

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.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

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: 269 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 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

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: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 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: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 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: 565 B

View file

@ -21,6 +21,7 @@
android:layout_marginLeft="-5dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:ignore="NegativeMargin"
tools:src="@tools:sample/avatars" />
<ImageView
@ -31,6 +32,7 @@
android:layout_marginLeft="-5dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:ignore="NegativeMargin"
tools:src="@tools:sample/avatars" />
<ImageView
@ -41,6 +43,7 @@
android:layout_marginLeft="-5dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:ignore="NegativeMargin"
tools:src="@tools:sample/avatars" />
<ImageView
@ -51,6 +54,7 @@
android:layout_marginLeft="-5dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:ignore="NegativeMargin"
tools:src="@tools:sample/avatars" />
</LinearLayout>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/ic_action_select_remove_group"
android:icon="@drawable/vector_leave_room_black"
android:title="@string/leave" />
</menu>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.matrix.vector.activity.RoomActivity">
<item
android:id="@+id/ic_action_search_in_room"
android:icon="@drawable/ic_material_search_white"
android:title="@string/room_menu_search"
app:showAsAction="always" />
<item
android:id="@+id/ic_action_matrix_apps"
android:icon="@drawable/apps_icon"
android:title="@string/room_add_matrix_apps"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/ic_action_room_resend_unsent"
android:icon="@drawable/ic_material_send_black"
android:title="@string/room_resend_unsent_messages"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/ic_action_room_delete_unsent"
android:icon="@drawable/ic_material_delete"
android:title="@string/room_delete_unsent_messages"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/ic_action_room_settings"
android:icon="@drawable/ic_material_settings_small"
android:title="@string/room_details_title"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/ic_action_room_leave"
android:icon="@drawable/vector_leave_room_black"
android:title="@string/leave"
app:showAsAction="collapseActionView" />
</menu>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.matrix.vector.activity.RoomActivity">
<item
android:id="@+id/ic_action_speak_to_search"
android:icon="@drawable/vector_micro_green"
android:title="@string/speak"
app:showAsAction="always" />
<item
android:id="@+id/ic_action_clear_search"
android:icon="@drawable/vector_clear_edittext_green"
android:title="@string/clear"
app:showAsAction="always" />
</menu>

View file

@ -26,7 +26,8 @@
<!-- BEGIN Strings added by Others -->
<string name="command_description_plain">Sends a message as plain text, without interpreting it as markdown</string>
<!-- END Strings added by Others -->
<string name="auth_invalid_login_param_space_in_password">Incorrect username and/or password. The entered password starts or ends with spaces, please check it.</string>
</resources>