Merge branch 'develop' into feature/misleading_url_target

This commit is contained in:
Onuray Sahin 2020-04-24 11:24:39 +03:00 committed by GitHub
commit f0648ee52a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 666 additions and 78 deletions

View File

@ -24,8 +24,10 @@ Improvements 🙌:
- Cross-Signing | Composer decoration: shields (#1077) - Cross-Signing | Composer decoration: shields (#1077)
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197) - Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
- Show a warning dialog if the text of the clicked link does not match the link target (#922) - Show a warning dialog if the text of the clicked link does not match the link target (#922)
- Cross-Signing | Consider not using a spinner on the 'complete security' prompt (#1271)
- Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719)) - Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719))
- Cross-Signing | Hide Use recovery key when 4S is not setup (#1007) - Cross-Signing | Hide Use recovery key when 4S is not setup (#1007)
- Cross-Signing | Trust account xSigning keys by entering Recovery Key (select file or copy) #1199
Bugfix 🐛: Bugfix 🐛:
- Fix summary notification staying after "mark as read" - Fix summary notification staying after "mark as read"

View File

@ -26,6 +26,8 @@ import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment
import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment
import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment
@ -456,4 +458,14 @@ interface FragmentModule {
@IntoMap @IntoMap
@FragmentKey(DeactivateAccountFragment::class) @FragmentKey(DeactivateAccountFragment::class)
fun bindDeactivateAccountFragment(fragment: DeactivateAccountFragment): Fragment fun bindDeactivateAccountFragment(fragment: DeactivateAccountFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SharedSecuredStoragePassphraseFragment::class)
fun bindSharedSecuredStoragePassphraseFragment(fragment: SharedSecuredStoragePassphraseFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SharedSecuredStorageKeyFragment::class)
fun bindSharedSecuredStorageKeyFragment(fragment: SharedSecuredStorageKeyFragment): Fragment
} }

View File

@ -23,8 +23,11 @@ import im.vector.riotx.core.platform.WaitingViewData
sealed class SharedSecureStorageAction : VectorViewModelAction { sealed class SharedSecureStorageAction : VectorViewModelAction {
object TogglePasswordVisibility : SharedSecureStorageAction() object TogglePasswordVisibility : SharedSecureStorageAction()
object UseKey : SharedSecureStorageAction()
object Back : SharedSecureStorageAction()
object Cancel : SharedSecureStorageAction() object Cancel : SharedSecureStorageAction()
data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction() data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction()
data class SubmitKey(val recoveryKey: String) : SharedSecureStorageAction()
} }
sealed class SharedSecureStorageViewEvent : VectorViewEvents { sealed class SharedSecureStorageViewEvent : VectorViewEvents {
@ -33,6 +36,7 @@ sealed class SharedSecureStorageViewEvent : VectorViewEvents {
data class FinishSuccess(val cypherResult: String) : SharedSecureStorageViewEvent() data class FinishSuccess(val cypherResult: String) : SharedSecureStorageViewEvent()
data class Error(val message: String, val dismiss: Boolean = false) : SharedSecureStorageViewEvent() data class Error(val message: String, val dismiss: Boolean = false) : SharedSecureStorageViewEvent()
data class InlineError(val message: String) : SharedSecureStorageViewEvent() data class InlineError(val message: String) : SharedSecureStorageViewEvent()
data class KeyInlineError(val message: String) : SharedSecureStorageViewEvent()
object ShowModalLoading : SharedSecureStorageViewEvent() object ShowModalLoading : SharedSecureStorageViewEvent()
object HideModalLoading : SharedSecureStorageViewEvent() object HideModalLoading : SharedSecureStorageViewEvent()
data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent() data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent()

View File

@ -23,17 +23,19 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.commitTransaction
import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.SimpleFragmentActivity
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity.* import kotlinx.android.synthetic.main.activity.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KClass
class SharedSecureStorageActivity : SimpleFragmentActivity() { class SharedSecureStorageActivity : SimpleFragmentActivity() {
@ -56,9 +58,6 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE toolbar.visibility = View.GONE
if (isFirstCreation()) {
addFragment(R.id.container, SharedSecuredStoragePassphraseFragment::class.java)
}
viewModel.viewEvents viewModel.viewEvents
.observe() .observe()
@ -69,10 +68,22 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
.disposeOnDestroy() .disposeOnDestroy()
viewModel.subscribe(this) { viewModel.subscribe(this) {
// renderState(it) renderState(it)
} }
} }
override fun onBackPressed() {
viewModel.handle(SharedSecureStorageAction.Back)
}
private fun renderState(state: SharedSecureStorageViewState) {
if (!state.ready) return
val fragment = if (state.hasPassphrase) {
if (state.useKey) SharedSecuredStorageKeyFragment::class else SharedSecuredStoragePassphraseFragment::class
} else SharedSecuredStorageKeyFragment::class
showFragment(fragment, Bundle())
}
private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { private fun observeViewEvents(it: SharedSecureStorageViewEvent?) {
when (it) { when (it) {
is SharedSecureStorageViewEvent.Dismiss -> { is SharedSecureStorageViewEvent.Dismiss -> {
@ -108,6 +119,18 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
} }
} }
private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
supportFragmentManager.commitTransaction {
replace(R.id.container,
fragmentClass.java,
bundle,
fragmentClass.simpleName
)
}
}
}
companion object { companion object {
const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT" const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT"
const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity" const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"

View File

@ -17,9 +17,14 @@
package im.vector.riotx.features.crypto.quads package im.vector.riotx.features.crypto.quads
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
@ -31,6 +36,7 @@ import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
@ -41,7 +47,11 @@ import timber.log.Timber
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
data class SharedSecureStorageViewState( data class SharedSecureStorageViewState(
val passphraseVisible: Boolean = false val ready: Boolean = false,
val hasPassphrase: Boolean = true,
val useKey: Boolean = false,
val passphraseVisible: Boolean = false,
val checkingSSSSAction: Async<Unit> = Uninitialized
) : MvRxState ) : MvRxState
class SharedSecureStorageViewModel @AssistedInject constructor( class SharedSecureStorageViewModel @AssistedInject constructor(
@ -66,6 +76,30 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
) )
) )
} }
val keyResult = args.keyId?.let { session.sharedSecretStorageService.getKey(it) }
?: session.sharedSecretStorageService.getDefaultKey()
if (!keyResult.isSuccess()) {
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
} else {
val info = (keyResult as KeyInfoResult.Success).keyInfo
if (info.content.passphrase != null) {
setState {
copy(
ready = true,
hasPassphrase = true,
useKey = false
)
}
} else {
setState {
copy(
ready = true,
hasPassphrase = false
)
}
}
}
} }
override fun handle(action: SharedSecureStorageAction) = withState { override fun handle(action: SharedSecureStorageAction) = withState {
@ -73,14 +107,98 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility()
is SharedSecureStorageAction.Cancel -> handleCancel() is SharedSecureStorageAction.Cancel -> handleCancel()
is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action)
SharedSecureStorageAction.UseKey -> handleUseKey()
is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action)
SharedSecureStorageAction.Back -> handleBack()
}.exhaustive
}
private fun handleUseKey() {
setState {
copy(
useKey = true
)
}
}
private fun handleBack() = withState { state ->
if (state.checkingSSSSAction is Loading) return@withState // ignore
if (state.hasPassphrase && state.useKey) {
setState {
copy(
useKey = false
)
}
} else {
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
}
}
private fun handleSubmitKey(action: SharedSecureStorageAction.SubmitKey) {
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
val decryptedSecretMap = HashMap<String, String>()
setState { copy(checkingSSSSAction = Loading()) }
viewModelScope.launch(Dispatchers.IO) {
runCatching {
val recoveryKey = action.recoveryKey
val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
if (!keyInfoResult.isSuccess()) {
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
_viewEvents.post(SharedSecureStorageViewEvent.Error(stringProvider.getString(R.string.failed_to_access_secure_storage)))
return@launch
}
val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
WaitingViewData(
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
isIndeterminate = true
)
))
val keySpec = RawBytesKeySpec.fromRecoveryKey(recoveryKey) ?: return@launch Unit.also {
_viewEvents.post(SharedSecureStorageViewEvent.KeyInlineError(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
}
withContext(Dispatchers.IO) {
args.requestedSecrets.forEach {
if (session.getAccountDataEvent(it) != null) {
val res = awaitCallback<String> { callback ->
session.sharedSecretStorageService.getSecret(
name = it,
keyId = keyInfo.id,
secretKey = keySpec,
callback = callback)
}
decryptedSecretMap[it] = res
} else {
Timber.w("## Cannot find secret $it in SSSS, skip")
}
}
}
}.fold({
setState { copy(checkingSSSSAction = Success(Unit)) }
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
val safeForIntentCypher = ByteArrayOutputStream().also {
it.use {
session.securelyStoreObject(decryptedSecretMap as Map<String, String>, args.resultKeyStoreAlias, it)
}
}.toByteArray().toBase64NoPadding()
_viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher))
}, {
setState { copy(checkingSSSSAction = Fail(it)) }
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
_viewEvents.post(SharedSecureStorageViewEvent.KeyInlineError(stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt)))
})
} }
} }
private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) {
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
val decryptedSecretMap = HashMap<String, String>() val decryptedSecretMap = HashMap<String, String>()
setState { copy(checkingSSSSAction = Loading()) }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
runCatching { runCatching {
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
val passphrase = action.passphrase val passphrase = action.passphrase
val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
if (!keyInfoResult.isSuccess()) { if (!keyInfoResult.isSuccess()) {
@ -100,7 +218,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
passphrase, passphrase,
keyInfo.content.passphrase?.salt ?: "", keyInfo.content.passphrase?.salt ?: "",
keyInfo.content.passphrase?.iterations ?: 0, keyInfo.content.passphrase?.iterations ?: 0,
// TODO
object : ProgressListener { object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
@ -132,6 +249,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
} }
} }
}.fold({ }.fold({
setState { copy(checkingSSSSAction = Success(Unit)) }
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
val safeForIntentCypher = ByteArrayOutputStream().also { val safeForIntentCypher = ByteArrayOutputStream().also {
it.use { it.use {
@ -140,6 +258,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
}.toByteArray().toBase64NoPadding() }.toByteArray().toBase64NoPadding()
_viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher))
}, { }, {
setState { copy(checkingSSSSAction = Fail(it)) }
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
_viewEvents.post(SharedSecureStorageViewEvent.InlineError(stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt))) _viewEvents.post(SharedSecureStorageViewEvent.InlineError(stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt)))
}) })

View File

@ -0,0 +1,125 @@
/*
* 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.
*/
package im.vector.riotx.features.crypto.quads
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.extensions.tryThis
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.startImportTextFromFileIntent
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SharedSecuredStorageKeyFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_key
val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ssss_restore_with_key_text.text = getString(R.string.enter_secret_storage_input_key)
ssss_key_enter_edittext.editorActionEvents()
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit()
}
}
.disposeOnDestroyView()
ssss_key_enter_edittext.textChanges()
.skipInitialValue()
.subscribe {
ssss_key_enter_til.error = null
ssss_key_submit.isEnabled = it.isNotBlank()
}
.disposeOnDestroyView()
ssss_key_use_file.clicks()
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startImportTextFromFileIntent(this, IMPORT_FILE_REQ)
}
.disposeOnDestroyView()
sharedViewModel.observeViewEvents {
when (it) {
is SharedSecureStorageViewEvent.KeyInlineError -> {
ssss_key_enter_til.error = it.message
}
}
}
ssss_key_submit.clicks()
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
submit()
}
.disposeOnDestroyView()
}
fun submit() {
val text = ssss_key_enter_edittext.text.toString()
if (text.isBlank()) return // Should not reach this point as button disabled
ssss_key_submit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
data?.data?.let { dataURI ->
tryThis {
activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader()
?.use { it.readText() }
?.let {
ssss_key_enter_edittext.setText(it)
}
}
}
return
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun invalidate() = withState(sharedViewModel) { _ ->
}
companion object {
private const val IMPORT_FILE_REQ = 0
}
}

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.crypto.quads
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.view.clicks
@ -27,12 +28,16 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.colorizeMatchingText
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.* import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.*
import me.gujun.android.span.span
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { class SharedSecuredStoragePassphraseFragment @Inject constructor(
private val colorProvider: ColorProvider
): VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase
@ -41,15 +46,17 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ssss_restore_with_passphrase_warning_text.text = span { // If has passphrase
span(getString(R.string.enter_secret_storage_passphrase_warning)) { val pass = getString(R.string.recovery_passphrase)
textStyle = "bold" val key = getString(R.string.recovery_key)
} ssss_restore_with_passphrase_warning_text.text = getString(
+" " R.string.enter_secret_storage_passphrase_or_key,
+getString(R.string.enter_secret_storage_passphrase_warning_text) pass,
} key
)
ssss_restore_with_passphrase_warning_reason.text = getString(R.string.enter_secret_storage_passphrase_reason_verify) .toSpannable()
.colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
.colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
ssss_passphrase_enter_edittext.editorActionEvents() ssss_passphrase_enter_edittext.editorActionEvents()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300, TimeUnit.MILLISECONDS)
@ -84,11 +91,11 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_passphrase_cancel.clicks() ssss_passphrase_use_key.clicks()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
sharedViewModel.handle(SharedSecureStorageAction.Cancel) sharedViewModel.handle(SharedSecureStorageAction.UseKey)
} }
.disposeOnDestroyView() .disposeOnDestroyView()

View File

@ -211,6 +211,12 @@ class BootstrapSharedViewModel @AssistedInject constructor(
) )
} }
} else { } else {
setState {
copy(
passphrase = null,
passphraseRepeat = null
)
}
startInitializeFlow(action.auth) startInitializeFlow(action.auth)
} }
} }
@ -438,7 +444,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
_viewEvents.post(BootstrapViewEvents.SkipBootstrap()) _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
} }
} }
is BootstrapStep.GetBackupSecretKeyForMigration -> { is BootstrapStep.GetBackupSecretKeyForMigration -> {
// do we let you cancel from here? // do we let you cancel from here?
_viewEvents.post(BootstrapViewEvents.SkipBootstrap()) _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
} }

View File

@ -324,10 +324,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
} }
override fun dismiss() {
super.dismiss()
}
companion object { companion object {
const val SECRET_REQUEST_CODE = 101 const val SECRET_REQUEST_CODE = 101

View File

@ -76,7 +76,7 @@ class VerificationCancelController @Inject constructor(
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("cancel") id("cancel")
title(stringProvider.getString(R.string.cancel)) title(stringProvider.getString(R.string.skip))
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
iconRes(R.drawable.ic_arrow_right) iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent)) iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))

View File

@ -0,0 +1,30 @@
/*
* 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.epoxy
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
/**
* A action for bottom sheet.
*/
@EpoxyModelClass(layout = R.layout.item_verification_wait)
abstract class BottomSheetSelfWaitItem : VectorEpoxyModel<BottomSheetSelfWaitItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View File

@ -27,6 +27,7 @@ import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.colorizeMatchingText
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetSelfWaitItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
@ -56,13 +57,12 @@ class VerificationRequestController @Inject constructor(
notice(stringProvider.getString(R.string.verification_open_other_to_verify)) notice(stringProvider.getString(R.string.verification_open_other_to_verify))
} }
dividerItem { bottomSheetSelfWaitItem {
id("sep") id("waiting")
} }
bottomSheetVerificationWaitingItem { dividerItem {
id("waiting") id("sep")
title(stringProvider.getString(R.string.verification_request_waiting, matrixItem.getBestName()))
} }
if (state.quadSContainsSecrets) { if (state.quadSContainsSecrets) {
@ -76,6 +76,19 @@ class VerificationRequestController @Inject constructor(
listener { listener?.onClickRecoverFromPassphrase() } listener { listener?.onClickRecoverFromPassphrase() }
} }
} }
dividerItem {
id("sep1")
}
bottomSheetVerificationActionItem {
id("skip")
title(stringProvider.getString(R.string.skip))
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
listener { listener?.onClickSkip() }
}
} else { } else {
val styledText = val styledText =
if (state.isMe) { if (state.isMe) {
@ -155,5 +168,6 @@ class VerificationRequestController @Inject constructor(
fun onClickOnWasNotMe() fun onClickOnWasNotMe()
fun onClickRecoverFromPassphrase() fun onClickRecoverFromPassphrase()
fun onClickDismiss() fun onClickDismiss()
fun onClickSkip()
} }
} }

View File

@ -70,6 +70,10 @@ class VerificationRequestFragment @Inject constructor(
viewModel.handle(VerificationAction.SkipVerification) viewModel.handle(VerificationAction.SkipVerification)
} }
override fun onClickSkip() {
viewModel.queryCancel()
}
override fun onClickOnWasNotMe() { override fun onClickOnWasNotMe() {
viewModel.itWasNotMe() viewModel.itWasNotMe()
} }

View File

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeWidth="1"
android:pathData="M2,5C2,3.8954 2.8954,3 4,3H20C21.1046,3 22,3.8954 22,5V15C22,16.1046 21.1046,17 20,17H4C2.8954,17 2,16.1046 2,15V5Z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M8,21H16"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M12,17V21"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeWidth="1"
android:pathData="M5,4C5,2.8954 5.8954,2 7,2H17C18.1046,2 19,2.8954 19,4V20C19,21.1046 18.1046,22 17,22H7C5.8954,22 5,21.1046 5,20V4Z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M12,18m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#2E2F32"/>
</vector>

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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"
android:id="@+id/ssss__root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ssss_shield"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:tint="?riotx_text_primary"
app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_key"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_key"
android:src="@drawable/ic_message_key" />
<TextView
android:id="@+id/ssss_restore_with_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="36dp"
android:layout_marginEnd="16dp"
android:text="@string/recovery_key"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ssss_shield"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/ssss_restore_with_key_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_key"
tools:text="@string/enter_secret_storage_input_key" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ssss_key_enter_til"
style="@style/VectorTextInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_key_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ssss_key_enter_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/recovery_key"
android:imeOptions="actionDone"
android:maxLines="3"
android:singleLine="false"
android:textColor="?android:textColorPrimary"
tools:inputType="textMultiLine" />
</com.google.android.material.textfield.TextInputLayout>
<!-- -->
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_key_use_file"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_file"
app:icon="@drawable/ic_file"
tools:ignore="MissingConstraints" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_key_submit"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/_continue"
tools:ignore="MissingConstraints" />
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
app:constraint_referenced_ids="ssss_key_use_file,ssss_key_submit"
app:flow_horizontalStyle="spread_inside"
app:flow_wrapMode="chain"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ssss_key_enter_til" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -12,14 +12,14 @@
<ImageView <ImageView
android:id="@+id/ssss_shield" android:id="@+id/ssss_shield"
android:layout_width="20dp" android:layout_width="24dp"
android:layout_height="20dp" android:layout_height="24dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:src="@drawable/key_big"
android:tint="?riotx_text_primary" android:tint="?riotx_text_primary"
app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_passphrase" app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_passphrase"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_passphrase" /> app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_passphrase"
android:src="@drawable/ic_message_password" />
<TextView <TextView
android:id="@+id/ssss_restore_with_passphrase" android:id="@+id/ssss_restore_with_passphrase"
@ -28,7 +28,7 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="36dp" android:layout_marginTop="36dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="@string/enter_secret_storage_passphrase" android:text="@string/recovery_passphrase"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
@ -47,20 +47,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase" app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase"
tools:text="@string/enter_secret_storage_passphrase_warning_text" /> tools:text="@string/enter_secret_storage_passphrase_or_key" />
<TextView
android:id="@+id/ssss_restore_with_passphrase_warning_reason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase_warning_text"
tools:text="@string/enter_secret_storage_passphrase_reason_verify" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
@ -74,7 +61,7 @@
app:errorEnabled="true" app:errorEnabled="true"
app:layout_constraintEnd_toStartOf="@id/ssss_view_show_password" app:layout_constraintEnd_toStartOf="@id/ssss_view_show_password"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase_warning_reason"> app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase_warning_text">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/ssss_passphrase_enter_edittext" android:id="@+id/ssss_passphrase_enter_edittext"
@ -102,30 +89,37 @@
app:layout_constraintStart_toEndOf="@+id/ssss_passphrase_enter_til" app:layout_constraintStart_toEndOf="@+id/ssss_passphrase_enter_til"
app:layout_constraintTop_toTopOf="@+id/ssss_passphrase_enter_til" /> app:layout_constraintTop_toTopOf="@+id/ssss_passphrase_enter_til" />
<!-- -->
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_passphrase_use_key"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_recovery_key"
app:icon="@drawable/ic_message_key"
tools:ignore="MissingConstraints" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/ssss_passphrase_submit" android:id="@+id/ssss_passphrase_submit"
style="@style/VectorButtonStylePositive" style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_marginTop="@dimen/layout_vertical_margin_big" android:layout_width="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
android:minWidth="200dp"
android:text="@string/_continue" android:text="@string/_continue"
app:layout_constraintEnd_toEndOf="parent" tools:ignore="MissingConstraints" />
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til" />
<androidx.constraintlayout.helper.widget.Flow
<com.google.android.material.button.MaterialButton android:layout_width="match_parent"
android:id="@+id/ssss_passphrase_cancel" android:layout_height="wrap_content"
style="@style/VectorButtonStyleDestructive" android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin_big" android:layout_marginBottom="@dimen/layout_vertical_margin_big"
android:minWidth="200dp" app:constraint_referenced_ids="ssss_passphrase_use_key,ssss_passphrase_submit"
android:text="@string/cancel" app:flow_horizontalStyle="spread_inside"
app:flow_wrapMode="chain"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til" />
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_submit" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/itemVerificationNoticeText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/use_other_session_content_description"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/use_latest_riot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text="@string/use_latest_riot"
android:textColor="?riotx_text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/monitorIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_monitor"
android:tint="?vctr_notice_secondary"
app:layout_constraintEnd_toStartOf="@id/smartphoneIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/use_latest_riot" />
<ImageView
android:id="@+id/smartphoneIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_smartphone"
android:tint="?vctr_notice_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/monitorIcon"
app:layout_constraintTop_toBottomOf="@+id/use_latest_riot" />
<TextView
android:id="@+id/riot_desktop_web"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/riot_desktop_web"
android:textColor="?riotx_text_primary"
app:layout_constraintEnd_toEndOf="@id/monitorIcon"
app:layout_constraintStart_toStartOf="@id/monitorIcon"
app:layout_constraintTop_toBottomOf="@id/monitorIcon" />
<TextView
android:id="@+id/riot_ios_android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/riot_ios_android"
android:textColor="?riotx_text_primary"
app:layout_constraintEnd_toEndOf="@id/smartphoneIcon"
app:layout_constraintStart_toStartOf="@id/smartphoneIcon"
app:layout_constraintTop_toBottomOf="@id/smartphoneIcon" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/labelsBarrier"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="riot_ios_android,riot_desktop_web" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/or_other_mx_capabale_client"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/labelsBarrier" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2251,7 +2251,6 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="enter_secret_storage_passphrase">Gib die geheime Speicherpassphrase ein</string> <string name="enter_secret_storage_passphrase">Gib die geheime Speicherpassphrase ein</string>
<string name="enter_secret_storage_passphrase_warning">Warnung:</string> <string name="enter_secret_storage_passphrase_warning">Warnung:</string>
<string name="enter_secret_storage_passphrase_warning_text">Du solltest nur von einem vertrauenswürdigen Gerät auf den geheimen Speicher zugreifen</string> <string name="enter_secret_storage_passphrase_warning_text">Du solltest nur von einem vertrauenswürdigen Gerät auf den geheimen Speicher zugreifen</string>
<string name="enter_secret_storage_passphrase_reason_verify">Greife auf deinen sicheren Nachrichtenverlauf und deine Cross-Signing-Identität zu, um andere Sitzungen zu überprüfen, indem du deine Passphrase eingibst</string>
<string name="message_action_item_redact">Entfernen…</string> <string name="message_action_item_redact">Entfernen…</string>
<string name="share_confirm_room">Möchtest du diesen Anhang an %1$s senden\?</string> <string name="share_confirm_room">Möchtest du diesen Anhang an %1$s senden\?</string>

View File

@ -2192,7 +2192,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.</string>
<string name="enter_secret_storage_passphrase">Sartu biltegi sekretuko pasa-esaldia</string> <string name="enter_secret_storage_passphrase">Sartu biltegi sekretuko pasa-esaldia</string>
<string name="enter_secret_storage_passphrase_warning">Abisua:</string> <string name="enter_secret_storage_passphrase_warning">Abisua:</string>
<string name="enter_secret_storage_passphrase_warning_text">Biltegi sekretura gailu fidagarri batetik konektatu beharko zinateke beti</string> <string name="enter_secret_storage_passphrase_warning_text">Biltegi sekretura gailu fidagarri batetik konektatu beharko zinateke beti</string>
<string name="enter_secret_storage_passphrase_reason_verify">Atzitu zure mezu seguruen historiala eta zeharkako sinatzerako identitatea beste saioak egiaztatzeko zure pasa-esaldia sartuz</string>
<string name="message_action_item_redact">Kendu…</string> <string name="message_action_item_redact">Kendu…</string>
<string name="share_confirm_room">Eranskin hau %1$s gelara bidali nahi duzu\?</string> <string name="share_confirm_room">Eranskin hau %1$s gelara bidali nahi duzu\?</string>

View File

@ -2201,7 +2201,6 @@ Si vous navez pas configuré de nouvelle méthode de récupération, un attaq
<string name="enter_secret_storage_passphrase">Saisir la phrase secrète du coffre secret</string> <string name="enter_secret_storage_passphrase">Saisir la phrase secrète du coffre secret</string>
<string name="enter_secret_storage_passphrase_warning">Attention :</string> <string name="enter_secret_storage_passphrase_warning">Attention :</string>
<string name="enter_secret_storage_passphrase_warning_text">Vous devriez accéder à votre coffre secret uniquement depuis un appareil de confiance</string> <string name="enter_secret_storage_passphrase_warning_text">Vous devriez accéder à votre coffre secret uniquement depuis un appareil de confiance</string>
<string name="enter_secret_storage_passphrase_reason_verify">Accédez à lhistorique de vos messages sécurisés et à votre identité de signature croisée pour vérifier dautres sessions en saisissant votre phrase secrète</string>
<string name="message_action_item_redact">Supprimer…</string> <string name="message_action_item_redact">Supprimer…</string>
<string name="share_confirm_room">Voulez-vous envoyer cette pièce jointe à %1$s \?</string> <string name="share_confirm_room">Voulez-vous envoyer cette pièce jointe à %1$s \?</string>

View File

@ -2196,7 +2196,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
<string name="enter_secret_storage_passphrase">Add meg a jelmondatot a biztonsági tárolóhoz</string> <string name="enter_secret_storage_passphrase">Add meg a jelmondatot a biztonsági tárolóhoz</string>
<string name="enter_secret_storage_passphrase_warning">Figyelem:</string> <string name="enter_secret_storage_passphrase_warning">Figyelem:</string>
<string name="enter_secret_storage_passphrase_warning_text">Csak biztonságos eszközről férj hozzá a biztonsági tárolóhoz</string> <string name="enter_secret_storage_passphrase_warning_text">Csak biztonságos eszközről férj hozzá a biztonsági tárolóhoz</string>
<string name="enter_secret_storage_passphrase_reason_verify">A jelmondat megadásával hozzáférhetsz a biztonságos üzeneteidhez és az eszközök közötti hitelesítéshez használt személyazonosságodhoz, hogy más munkameneteket hitelesíthess</string>
<string name="message_action_item_redact">Töröl…</string> <string name="message_action_item_redact">Töröl…</string>
<string name="share_confirm_room">Ezt a csatolmányt el szeretnéd küldeni ide: %1$s\?</string> <string name="share_confirm_room">Ezt a csatolmányt el szeretnéd küldeni ide: %1$s\?</string>

View File

@ -2246,7 +2246,6 @@
<string name="enter_secret_storage_passphrase">Inserisci la password dell\'archivio segreto</string> <string name="enter_secret_storage_passphrase">Inserisci la password dell\'archivio segreto</string>
<string name="enter_secret_storage_passphrase_warning">Attenzione:</string> <string name="enter_secret_storage_passphrase_warning">Attenzione:</string>
<string name="enter_secret_storage_passphrase_warning_text">Dovresti accedere all\'archivio segreto solo da un dispositivo fidato</string> <string name="enter_secret_storage_passphrase_warning_text">Dovresti accedere all\'archivio segreto solo da un dispositivo fidato</string>
<string name="enter_secret_storage_passphrase_reason_verify">Accedi alla cronologia dei messaggi sicuri e all\'identità di firma incrociata per verificare altre sessioni inserendo la tua password</string>
<string name="message_action_item_redact">Rimuovi…</string> <string name="message_action_item_redact">Rimuovi…</string>
<string name="share_confirm_room">Vuoi inviare questo allegato a %1$s\?</string> <string name="share_confirm_room">Vuoi inviare questo allegato a %1$s\?</string>

View File

@ -2172,7 +2172,6 @@ Spróbuj uruchomić ponownie aplikację.</string>
<string name="enter_secret_storage_passphrase">Wprowadź hasło tajemnej przestrzeni</string> <string name="enter_secret_storage_passphrase">Wprowadź hasło tajemnej przestrzeni</string>
<string name="enter_secret_storage_passphrase_warning">Ostrzeżenie:</string> <string name="enter_secret_storage_passphrase_warning">Ostrzeżenie:</string>
<string name="enter_secret_storage_passphrase_warning_text">Powinieneś(-nnaś) uzyskać dostęp do tajnej przestrzeni jedynie z zaufanego urządzenia</string> <string name="enter_secret_storage_passphrase_warning_text">Powinieneś(-nnaś) uzyskać dostęp do tajnej przestrzeni jedynie z zaufanego urządzenia</string>
<string name="enter_secret_storage_passphrase_reason_verify">Uzyskaj dostęp do historii bezpiecznych wiadomości i Twojego(-jej) tożsamości podpisu krzyżowego poprzez weryfikację innej sesji za pomocą hasła</string>
<string name="message_action_item_redact">Usuń…</string> <string name="message_action_item_redact">Usuń…</string>
<string name="share_confirm_room">Czy chcesz wysłać załącznik do %1$s\?</string> <string name="share_confirm_room">Czy chcesz wysłać załącznik do %1$s\?</string>

View File

@ -2156,7 +2156,6 @@ Që të garantoni se sju shpëton gjë, thjesht mbajeni të aktivizuar mekani
<string name="enter_secret_storage_passphrase">Jepni frazëkalimin e fshehtë për në depozitim</string> <string name="enter_secret_storage_passphrase">Jepni frazëkalimin e fshehtë për në depozitim</string>
<string name="enter_secret_storage_passphrase_warning">Kujdes:</string> <string name="enter_secret_storage_passphrase_warning">Kujdes:</string>
<string name="enter_secret_storage_passphrase_warning_text">Duhet të hyni në depozitim të fshehtë vetëm nga një pajisje e besuar</string> <string name="enter_secret_storage_passphrase_warning_text">Duhet të hyni në depozitim të fshehtë vetëm nga një pajisje e besuar</string>
<string name="enter_secret_storage_passphrase_reason_verify">Për verifikim sesionesh të tjerë përmes dhënies së frazëkalimit tuaj, hyni te historiku i mesazheve tuaj të sigurt dhe identiteti juaj për <em>cross-signing</em></string>
<string name="message_action_item_redact">Hiqni…</string> <string name="message_action_item_redact">Hiqni…</string>
<string name="share_confirm_room">Doni të dërgohet kjo bashkëngjitje te %1$s\?</string> <string name="share_confirm_room">Doni të dërgohet kjo bashkëngjitje te %1$s\?</string>

View File

@ -2144,7 +2144,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
<string name="enter_secret_storage_passphrase">輸入秘密儲存空間通關密語</string> <string name="enter_secret_storage_passphrase">輸入秘密儲存空間通關密語</string>
<string name="enter_secret_storage_passphrase_warning">警告:</string> <string name="enter_secret_storage_passphrase_warning">警告:</string>
<string name="enter_secret_storage_passphrase_warning_text">您僅能從受信任的裝置存取秘密儲存空間</string> <string name="enter_secret_storage_passphrase_warning_text">您僅能從受信任的裝置存取秘密儲存空間</string>
<string name="enter_secret_storage_passphrase_reason_verify">透過輸入通關密語來存取您的安全訊息歷史與您的交叉簽章身份以驗證其他工作階段</string>
<string name="message_action_item_redact">移除……</string> <string name="message_action_item_redact">移除……</string>
<string name="share_confirm_room">您想要傳送此附件到 %1$s 嗎?</string> <string name="share_confirm_room">您想要傳送此附件到 %1$s 嗎?</string>

View File

@ -2166,7 +2166,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
</plurals> </plurals>
<string name="poll_item_selected_aria">Selected Option</string> <string name="poll_item_selected_aria">Selected Option</string>
<string name="command_description_poll">Creates a simple poll</string> <string name="command_description_poll">Creates a simple poll</string>
<string name="verification_cannot_access_other_session">Use a recovery method</string> <string name="verification_cannot_access_other_session">Use a Recovery Passphrase or Key</string>
<string name="verification_use_passphrase">If you cant access an existing session</string> <string name="verification_use_passphrase">If you cant access an existing session</string>
<string name="new_signin">New Sign In</string> <string name="new_signin">New Sign In</string>
@ -2175,7 +2175,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="enter_secret_storage_passphrase">Enter secret storage passphrase</string> <string name="enter_secret_storage_passphrase">Enter secret storage passphrase</string>
<string name="enter_secret_storage_passphrase_warning">Warning:</string> <string name="enter_secret_storage_passphrase_warning">Warning:</string>
<string name="enter_secret_storage_passphrase_warning_text">You should only access secret storage from a trusted device</string> <string name="enter_secret_storage_passphrase_warning_text">You should only access secret storage from a trusted device</string>
<string name="enter_secret_storage_passphrase_reason_verify">Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase</string>
<string name="message_action_item_redact">Remove…</string> <string name="message_action_item_redact">Remove…</string>
<string name="share_confirm_room">Do you want to send this attachment to %1$s?</string> <string name="share_confirm_room">Do you want to send this attachment to %1$s?</string>

View File

@ -6,8 +6,19 @@
<!-- Sections has been created to limit merge conflicts. --> <!-- Sections has been created to limit merge conflicts. -->
<!-- BEGIN Strings added by Valere --> <!-- BEGIN Strings added by Valere -->
<string name="use_other_session_content_description">Use the latest Riot on your other devices, Riot Web, Riot Desktop, Riot iOS, RiotX for Android, or another cross-signing capable Matrix client</string>
<string name="riot_desktop_web">Riot Web\nRiot Desktop</string>
<string name="riot_ios_android">Riot iOS\nRiot X for Android</string>
<string name="or_other_mx_capabale_client">or another cross-signing capable Matrix client</string>
<string name="use_latest_riot">Use the latest Riot on your other devices:</string>
<string name="command_description_discard_session">Forces the current outbound group session in an encrypted room to be discarded</string> <string name="command_description_discard_session">Forces the current outbound group session in an encrypted room to be discarded</string>
<string name="command_description_discard_session_not_handled">Only supported in encrypted rooms</string> <string name="command_description_discard_session_not_handled">Only supported in encrypted rooms</string>
<!-- first will be replaced by recovery_passphrase, second will be replaced by recovery_key-->
<string name="enter_secret_storage_passphrase_or_key">Use your %1$s or use your %2$s to continue.</string>
<string name="use_recovery_key">Use Recovery Key</string>
<string name="enter_secret_storage_input_key">Select your Recovery Key, or input it manually by typing it or pasting from your clipboard</string>
<string name="keys_backup_recovery_key_error_decrypt">Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key.</string>
<string name="failed_to_access_secure_storage">Failed to access secure storage</string>
<!-- END Strings added by Valere --> <!-- END Strings added by Valere -->