diff --git a/CHANGES.md b/CHANGES.md index e727452189..550d013ba3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,8 +24,10 @@ Improvements 🙌: - Cross-Signing | Composer decoration: shields (#1077) - 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) + - 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)) - 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 🐛: - Fix summary notification staying after "mark as read" diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index d22d80c4b3..db62ddc2d8 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -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.CreateDirectRoomKnownUsersFragment 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.BootstrapConclusionFragment import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment @@ -456,4 +458,14 @@ interface FragmentModule { @IntoMap @FragmentKey(DeactivateAccountFragment::class) 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 } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt index c2c2b9c42a..876c758864 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt @@ -23,8 +23,11 @@ import im.vector.riotx.core.platform.WaitingViewData sealed class SharedSecureStorageAction : VectorViewModelAction { object TogglePasswordVisibility : SharedSecureStorageAction() + object UseKey : SharedSecureStorageAction() + object Back : SharedSecureStorageAction() object Cancel : SharedSecureStorageAction() data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction() + data class SubmitKey(val recoveryKey: String) : SharedSecureStorageAction() } sealed class SharedSecureStorageViewEvent : VectorViewEvents { @@ -33,6 +36,7 @@ sealed class SharedSecureStorageViewEvent : VectorViewEvents { data class FinishSuccess(val cypherResult: String) : SharedSecureStorageViewEvent() data class Error(val message: String, val dismiss: Boolean = false) : SharedSecureStorageViewEvent() data class InlineError(val message: String) : SharedSecureStorageViewEvent() + data class KeyInlineError(val message: String) : SharedSecureStorageViewEvent() object ShowModalLoading : SharedSecureStorageViewEvent() object HideModalLoading : SharedSecureStorageViewEvent() data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt index 1347b6ca19..db37107dd6 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt @@ -23,17 +23,19 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent 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 io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity.* import javax.inject.Inject +import kotlin.reflect.KClass class SharedSecureStorageActivity : SimpleFragmentActivity() { @@ -56,9 +58,6 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) toolbar.visibility = View.GONE - if (isFirstCreation()) { - addFragment(R.id.container, SharedSecuredStoragePassphraseFragment::class.java) - } viewModel.viewEvents .observe() @@ -69,10 +68,22 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { .disposeOnDestroy() 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?) { when (it) { is SharedSecureStorageViewEvent.Dismiss -> { @@ -108,6 +119,18 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { } } + private fun showFragment(fragmentClass: KClass, bundle: Bundle) { + if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { + supportFragmentManager.commitTransaction { + replace(R.id.container, + fragmentClass.java, + bundle, + fragmentClass.simpleName + ) + } + } + } + companion object { const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT" const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity" diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt index 530e0934cb..314f187ab9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -17,9 +17,14 @@ package im.vector.riotx.features.crypto.quads 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.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted 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.util.awaitCallback 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.WaitingViewData import im.vector.riotx.core.resources.StringProvider @@ -41,7 +47,11 @@ import timber.log.Timber import java.io.ByteArrayOutputStream 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 = Uninitialized ) : MvRxState 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 { @@ -73,14 +107,98 @@ class SharedSecureStorageViewModel @AssistedInject constructor( is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() is SharedSecureStorageAction.Cancel -> handleCancel() 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() + 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 { 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, 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) { + _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) val decryptedSecretMap = HashMap() + setState { copy(checkingSSSSAction = Loading()) } viewModelScope.launch(Dispatchers.IO) { runCatching { - _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) val passphrase = action.passphrase val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() if (!keyInfoResult.isSuccess()) { @@ -100,7 +218,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor( passphrase, keyInfo.content.passphrase?.salt ?: "", keyInfo.content.passphrase?.iterations ?: 0, - // TODO object : ProgressListener { override fun onProgress(progress: Int, total: Int) { _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( @@ -132,6 +249,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } } }.fold({ + setState { copy(checkingSSSSAction = Success(Unit)) } _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) val safeForIntentCypher = ByteArrayOutputStream().also { it.use { @@ -140,6 +258,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( }.toByteArray().toBase64NoPadding() _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) }, { + setState { copy(checkingSSSSAction = Fail(it)) } _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.InlineError(stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt))) }) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt new file mode 100644 index 0000000000..db4c5230fd --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index f7a5e7c1bc..82d27aea1b 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -19,6 +19,7 @@ package im.vector.riotx.features.crypto.quads import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo +import androidx.core.text.toSpannable import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState 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.core.extensions.showPassword 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 kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.* -import me.gujun.android.span.span 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 @@ -41,15 +46,17 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - ssss_restore_with_passphrase_warning_text.text = span { - span(getString(R.string.enter_secret_storage_passphrase_warning)) { - textStyle = "bold" - } - +" " - +getString(R.string.enter_secret_storage_passphrase_warning_text) - } - - ssss_restore_with_passphrase_warning_reason.text = getString(R.string.enter_secret_storage_passphrase_reason_verify) + // If has passphrase + val pass = getString(R.string.recovery_passphrase) + val key = getString(R.string.recovery_key) + ssss_restore_with_passphrase_warning_text.text = getString( + R.string.enter_secret_storage_passphrase_or_key, + pass, + key + ) + .toSpannable() + .colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) ssss_passphrase_enter_edittext.editorActionEvents() .debounce(300, TimeUnit.MILLISECONDS) @@ -84,11 +91,11 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { } .disposeOnDestroyView() - ssss_passphrase_cancel.clicks() + ssss_passphrase_use_key.clicks() .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { - sharedViewModel.handle(SharedSecureStorageAction.Cancel) + sharedViewModel.handle(SharedSecureStorageAction.UseKey) } .disposeOnDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 55481b1f5b..6d96d9038e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -211,6 +211,12 @@ class BootstrapSharedViewModel @AssistedInject constructor( ) } } else { + setState { + copy( + passphrase = null, + passphraseRepeat = null + ) + } startInitializeFlow(action.auth) } } @@ -438,7 +444,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } } - is BootstrapStep.GetBackupSecretKeyForMigration -> { + is BootstrapStep.GetBackupSecretKeyForMigration -> { // do we let you cancel from here? _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 695716d386..e07150ed4f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -324,10 +324,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } - override fun dismiss() { - super.dismiss() - } - companion object { const val SECRET_REQUEST_CODE = 101 diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt index 49b2f7dce1..73f449a3c3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt @@ -76,7 +76,7 @@ class VerificationCancelController @Inject constructor( bottomSheetVerificationActionItem { id("cancel") - title(stringProvider.getString(R.string.cancel)) + 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)) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetSelfWaitItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetSelfWaitItem.kt new file mode 100644 index 0000000000..6afc330f62 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetSelfWaitItem.kt @@ -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() { + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt index a1b55832d5..88f6607a41 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt @@ -27,6 +27,7 @@ import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.colorizeMatchingText 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.bottomSheetVerificationNoticeItem 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)) } - dividerItem { - id("sep") + bottomSheetSelfWaitItem { + id("waiting") } - bottomSheetVerificationWaitingItem { - id("waiting") - title(stringProvider.getString(R.string.verification_request_waiting, matrixItem.getBestName())) + dividerItem { + id("sep") } if (state.quadSContainsSecrets) { @@ -76,6 +76,19 @@ class VerificationRequestController @Inject constructor( 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 { val styledText = if (state.isMe) { @@ -155,5 +168,6 @@ class VerificationRequestController @Inject constructor( fun onClickOnWasNotMe() fun onClickRecoverFromPassphrase() fun onClickDismiss() + fun onClickSkip() } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt index b6c3659988..967f2946c1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt @@ -70,6 +70,10 @@ class VerificationRequestFragment @Inject constructor( viewModel.handle(VerificationAction.SkipVerification) } + override fun onClickSkip() { + viewModel.queryCancel() + } + override fun onClickOnWasNotMe() { viewModel.itWasNotMe() } diff --git a/vector/src/main/res/drawable/ic_monitor.xml b/vector/src/main/res/drawable/ic_monitor.xml new file mode 100644 index 0000000000..c6bbe52171 --- /dev/null +++ b/vector/src/main/res/drawable/ic_monitor.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_smartphone.xml b/vector/src/main/res/drawable/ic_smartphone.xml new file mode 100644 index 0000000000..3f1072ce16 --- /dev/null +++ b/vector/src/main/res/drawable/ic_smartphone.xml @@ -0,0 +1,17 @@ + + + + diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_key.xml b/vector/src/main/res/layout/fragment_ssss_access_from_key.xml new file mode 100644 index 0000000000..2f6945bc5b --- /dev/null +++ b/vector/src/main/res/layout/fragment_ssss_access_from_key.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml index db50cfd34d..6192e87259 100644 --- a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml +++ b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml @@ -12,14 +12,14 @@ + app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_passphrase" + android:src="@drawable/ic_message_password" /> - - - + tools:text="@string/enter_secret_storage_passphrase_or_key" /> + app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase_warning_text"> + + + + + tools:ignore="MissingConstraints" /> - - + app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_verification_wait.xml b/vector/src/main/res/layout/item_verification_wait.xml new file mode 100644 index 0000000000..a5c4ae6f1b --- /dev/null +++ b/vector/src/main/res/layout/item_verification_wait.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index c7957dbd5b..f6011555f8 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2251,7 +2251,6 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine Gib die geheime Speicherpassphrase ein Warnung: Du solltest nur von einem vertrauenswürdigen Gerät auf den geheimen Speicher zugreifen - Greife auf deinen sicheren Nachrichtenverlauf und deine Cross-Signing-Identität zu, um andere Sitzungen zu überprüfen, indem du deine Passphrase eingibst Entfernen… Möchtest du diesen Anhang an %1$s senden\? diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index f6a4ab139f..7c42f6a71a 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2192,7 +2192,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Sartu biltegi sekretuko pasa-esaldia Abisua: Biltegi sekretura gailu fidagarri batetik konektatu beharko zinateke beti - Atzitu zure mezu seguruen historiala eta zeharkako sinatzerako identitatea beste saioak egiaztatzeko zure pasa-esaldia sartuz Kendu… Eranskin hau %1$s gelara bidali nahi duzu\? diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 393e34604d..81ce77cf15 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2201,7 +2201,6 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Saisir la phrase secrète du coffre secret Attention : Vous devriez accéder à votre coffre secret uniquement depuis un appareil de confiance - Accédez à l’historique de vos messages sécurisés et à votre identité de signature croisée pour vérifier d’autres sessions en saisissant votre phrase secrète Supprimer… Voulez-vous envoyer cette pièce jointe à %1$s \? diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index f0bd199894..e83e396883 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -2196,7 +2196,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Add meg a jelmondatot a biztonsági tárolóhoz Figyelem: Csak biztonságos eszközről férj hozzá a biztonsági tárolóhoz - 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 Töröl… Ezt a csatolmányt el szeretnéd küldeni ide: %1$s\? diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index d673406407..ecacb45994 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2246,7 +2246,6 @@ Inserisci la password dell\'archivio segreto Attenzione: Dovresti accedere all\'archivio segreto solo da un dispositivo fidato - Accedi alla cronologia dei messaggi sicuri e all\'identità di firma incrociata per verificare altre sessioni inserendo la tua password Rimuovi… Vuoi inviare questo allegato a %1$s\? diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index a747e92ee0..b0c1492acb 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -2172,7 +2172,6 @@ Spróbuj uruchomić ponownie aplikację. Wprowadź hasło tajemnej przestrzeni Ostrzeżenie: Powinieneś(-nnaś) uzyskać dostęp do tajnej przestrzeni jedynie z zaufanego urządzenia - 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 Usuń… Czy chcesz wysłać załącznik do %1$s\? diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 453de4cda3..970587e702 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2156,7 +2156,6 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Jepni frazëkalimin e fshehtë për në depozitim Kujdes: Duhet të hyni në depozitim të fshehtë vetëm nga një pajisje e besuar - 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 cross-signing Hiqni… Doni të dërgohet kjo bashkëngjitje te %1$s\? diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index dc8ed59ea9..edefeec974 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2144,7 +2144,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 輸入秘密儲存空間通關密語 警告: 您僅能從受信任的裝置存取秘密儲存空間 - 透過輸入通關密語來存取您的安全訊息歷史與您的交叉簽章身份以驗證其他工作階段 移除…… 您想要傳送此附件到 %1$s 嗎? diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b37e7fd729..370b7cf8f4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2166,7 +2166,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Selected Option Creates a simple poll - Use a recovery method + Use a Recovery Passphrase or Key If you can’t access an existing session New Sign In @@ -2175,7 +2175,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Enter secret storage passphrase Warning: You should only access secret storage from a trusted device - Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase Remove… Do you want to send this attachment to %1$s? diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7a851025c9..8b675ee8c1 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -6,8 +6,19 @@ + Use the latest Riot on your other devices, Riot Web, Riot Desktop, Riot iOS, RiotX for Android, or another cross-signing capable Matrix client + Riot Web\nRiot Desktop + Riot iOS\nRiot X for Android + or another cross-signing capable Matrix client + Use the latest Riot on your other devices: Forces the current outbound group session in an encrypted room to be discarded Only supported in encrypted rooms + + Use your %1$s or use your %2$s to continue. + Use Recovery Key + Select your Recovery Key, or input it manually by typing it or pasting from your clipboard + Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key. + Failed to access secure storage