Bootstrap: Add an introduction step: BootstrapSetupRecoveryKeyFragment

This commit is contained in:
Benoit Marty 2020-06-16 18:02:55 +02:00 committed by Valere
parent 369f40c804
commit f3b464b88a
21 changed files with 226 additions and 40 deletions

View file

@ -30,6 +30,7 @@ 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.BootstrapMigrateBackupFragment import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment
import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment
import im.vector.riotx.features.crypto.recover.BootstrapSetupRecoveryKeyFragment
import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment
import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment
import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment
@ -456,6 +457,11 @@ interface FragmentModule {
@FragmentKey(BootstrapWaitingFragment::class) @FragmentKey(BootstrapWaitingFragment::class)
fun bindBootstrapWaitingFragment(fragment: BootstrapWaitingFragment): Fragment fun bindBootstrapWaitingFragment(fragment: BootstrapWaitingFragment): Fragment
@Binds
@IntoMap
@FragmentKey(BootstrapSetupRecoveryKeyFragment::class)
fun bindBootstrapSetupRecoveryKeyFragment(fragment: BootstrapSetupRecoveryKeyFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(BootstrapSaveRecoveryKeyFragment::class) @FragmentKey(BootstrapSaveRecoveryKeyFragment::class)

View file

@ -27,6 +27,8 @@ sealed class BootstrapActions : VectorViewModelAction {
object GoToCompleted : BootstrapActions() object GoToCompleted : BootstrapActions()
object GoToEnterAccountPassword : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions()
object SetupRecoveryKey : BootstrapActions()
object DoInitializeGeneratedKey : BootstrapActions() object DoInitializeGeneratedKey : BootstrapActions()
object TogglePasswordVisibility : BootstrapActions() object TogglePasswordVisibility : BootstrapActions()
data class ReAuth(val pass: String) : BootstrapActions() data class ReAuth(val pass: String) : BootstrapActions()

View file

@ -121,8 +121,12 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
when (state.step) { when (state.step) {
is BootstrapStep.SetupSecureBackup -> {
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp))
bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
showFragment(BootstrapSetupRecoveryKeyFragment::class, Bundle())
}
is BootstrapStep.CheckingMigration -> { is BootstrapStep.CheckingMigration -> {
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
bootstrapTitleText.text = getString(R.string.upgrade_security) bootstrapTitleText.text = getString(R.string.upgrade_security)
@ -134,17 +138,17 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
showFragment(BootstrapAccountPasswordFragment::class, Bundle()) showFragment(BootstrapAccountPasswordFragment::class, Bundle())
} }
is BootstrapStep.Initializing -> { is BootstrapStep.Initializing -> {
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp))
bootstrapTitleText.text = getString(R.string.bootstrap_loading_title) bootstrapTitleText.text = getString(R.string.bootstrap_loading_title)
showFragment(BootstrapWaitingFragment::class, Bundle()) showFragment(BootstrapWaitingFragment::class, Bundle())
} }
is BootstrapStep.SaveRecoveryKey -> { is BootstrapStep.SaveRecoveryKey -> {
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp))
bootstrapTitleText.text = getString(R.string.keys_backup_setup_step3_please_make_copy) bootstrapTitleText.text = getString(R.string.bottom_sheet_save_your_recovery_key_title)
showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle()) showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle())
} }
is BootstrapStep.DoneSuccess -> { is BootstrapStep.DoneSuccess -> {
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp))
bootstrapTitleText.text = getString(R.string.bootstrap_finish_title) bootstrapTitleText.text = getString(R.string.bootstrap_finish_title)
showFragment(BootstrapConclusionFragment::class, Bundle()) showFragment(BootstrapConclusionFragment::class, Bundle())
} }
@ -153,7 +157,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey
else -> true else -> true
} }
val drawableRes = if (isKey) R.drawable.ic_message_key else R.drawable.ic_message_password val drawableRes = if (isKey) R.drawable.ic_secure_backup_24dp else R.drawable.ic_message_password
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable( bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(
requireContext(), requireContext(),
drawableRes) drawableRes)

View file

@ -16,10 +16,8 @@
package im.vector.riotx.features.crypto.recover package im.vector.riotx.features.crypto.recover
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.failure.toRegistrationFlowResponse
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@ -71,19 +69,23 @@ data class Params(
val keySpec: SsssKeySpec? = null val keySpec: SsssKeySpec? = null
) )
// TODO Rename to CreateServerRecovery
class BootstrapCrossSigningTask @Inject constructor( class BootstrapCrossSigningTask @Inject constructor(
private val session: Session, private val session: Session,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : ViewModelTask<Params, BootstrapResult> { ) : ViewModelTask<Params, BootstrapResult> {
override suspend fun execute(params: Params): BootstrapResult { override suspend fun execute(params: Params): BootstrapResult {
val crossSigningService = session.cryptoService().crossSigningService()
// TODO Remove
/*
params.progressListener?.onProgress( params.progressListener?.onProgress(
WaitingViewData( WaitingViewData(
stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing), stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing),
isIndeterminate = true isIndeterminate = true
) )
) )
val crossSigningService = session.cryptoService().crossSigningService()
try { try {
awaitCallback<Unit> { awaitCallback<Unit> {
@ -92,6 +94,7 @@ class BootstrapCrossSigningTask @Inject constructor(
} catch (failure: Throwable) { } catch (failure: Throwable) {
return handleInitializeXSigningError(failure) return handleInitializeXSigningError(failure)
} }
*/
val keyInfo: SsssKeyCreationInfo val keyInfo: SsssKeyCreationInfo
@ -214,6 +217,8 @@ class BootstrapCrossSigningTask @Inject constructor(
return BootstrapResult.Success(keyInfo) return BootstrapResult.Success(keyInfo)
} }
/*
TODO Remove
private fun handleInitializeXSigningError(failure: Throwable): BootstrapResult { private fun handleInitializeXSigningError(failure: Throwable): BootstrapResult {
if (failure is Failure.ServerError && failure.error.code == MatrixError.M_FORBIDDEN) { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_FORBIDDEN) {
return BootstrapResult.InvalidPasswordError(failure.error) return BootstrapResult.InvalidPasswordError(failure.error)
@ -230,4 +235,5 @@ class BootstrapCrossSigningTask @Inject constructor(
} }
return BootstrapResult.GenericError(failure) return BootstrapResult.GenericError(failure)
} }
*/
} }

View file

@ -21,14 +21,12 @@ import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.colorizeMatchingText
import im.vector.riotx.core.utils.startSharePlainTextIntent import im.vector.riotx.core.utils.startSharePlainTextIntent
import im.vector.riotx.core.utils.toast import im.vector.riotx.core.utils.toast
import kotlinx.android.synthetic.main.fragment_bootstrap_save_key.* import kotlinx.android.synthetic.main.fragment_bootstrap_save_key.*
@ -48,14 +46,6 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val messageKey = getString(R.string.message_key)
val recoveryPassphrase = getString(R.string.recovery_passphrase)
val color = colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_link_text_color)
bootstrapSaveText.text = getString(R.string.bootstrap_save_key_description, messageKey, recoveryPassphrase)
.toSpannable()
.colorizeMatchingText(messageKey, color)
.colorizeMatchingText(recoveryPassphrase, color)
recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() } recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() }
recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() } recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() }
recoveryContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.GoToCompleted) } recoveryContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.GoToCompleted) }

View file

@ -0,0 +1,42 @@
/*
* 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.recover
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.parentFragmentViewModel
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_bootstrap_setup_recovery.*
import javax.inject.Inject
class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_setup_recovery
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bootstrapSetupSecureSubmit.clickableView.debouncedClicks { setupRecoveryKey() }
}
private fun setupRecoveryKey() {
sharedViewModel.handle(BootstrapActions.SetupRecoveryKey)
}
}

View file

@ -17,11 +17,9 @@
package im.vector.riotx.features.crypto.recover package im.vector.riotx.features.crypto.recover
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
@ -31,7 +29,6 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
@ -46,15 +43,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.OutputStream import java.io.OutputStream
data class BootstrapViewState(
val step: BootstrapStep = BootstrapStep.AccountPassword(false),
val migrationRecoveryKey: String? = null,
val crossSigningInitialization: Async<Unit> = Uninitialized,
val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null,
val initializationWaitingViewData: WaitingViewData? = null,
val recoverySaveFileProcess: Async<Unit> = Uninitialized
) : MvRxState
class BootstrapSharedViewModel @AssistedInject constructor( class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState, @Assisted initialState: BootstrapViewState,
@Assisted val args: BootstrapBottomSheet.Args, @Assisted val args: BootstrapBottomSheet.Args,
@ -72,7 +60,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
private var _pendingSession: String? = null private var _pendingSession: String? = null
init { private fun startProcess() {
// need to check if user have an existing keybackup // need to check if user have an existing keybackup
if (args.isNewAccount) { if (args.isNewAccount) {
setState { setState {
@ -137,6 +125,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
} }
} }
} }
BootstrapActions.SetupRecoveryKey -> {
startProcess()
}
is BootstrapActions.DoInitializeGeneratedKey -> { is BootstrapActions.DoInitializeGeneratedKey -> {
val userPassword = reAuthHelper.data val userPassword = reAuthHelper.data
if (userPassword == null) { if (userPassword == null) {

View file

@ -17,6 +17,12 @@
package im.vector.riotx.features.crypto.recover package im.vector.riotx.features.crypto.recover
/** /**
*
* BootstrapStep.SetupSecureBackup
*
*
*
*
* *
* User has signing keys? Account * User has signing keys? Account
* Creation ? * Creation ?
@ -73,6 +79,8 @@ package im.vector.riotx.features.crypto.recover
*/ */
sealed class BootstrapStep { sealed class BootstrapStep {
object SetupSecureBackup : BootstrapStep()
data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep() data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
object CheckingMigration : BootstrapStep() object CheckingMigration : BootstrapStep()

View file

@ -0,0 +1,32 @@
/*
* 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.recover
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
import im.vector.riotx.core.platform.WaitingViewData
data class BootstrapViewState(
val step: BootstrapStep = BootstrapStep.SetupSecureBackup,
val migrationRecoveryKey: String? = null,
val crossSigningInitialization: Async<Unit> = Uninitialized,
val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null,
val initializationWaitingViewData: WaitingViewData? = null,
val recoverySaveFileProcess: Async<Unit> = Uninitialized
) : MvRxState

View file

@ -19,5 +19,6 @@ package im.vector.riotx.features.settings.crosssigning
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
sealed class CrossSigningSettingsAction : VectorViewModelAction { sealed class CrossSigningSettingsAction : VectorViewModelAction {
object SetUpRecovery : CrossSigningSettingsAction()
object VerifySession : CrossSigningSettingsAction() object VerifySession : CrossSigningSettingsAction()
} }

View file

@ -33,6 +33,7 @@ class CrossSigningSettingsController @Inject constructor(
) : TypedEpoxyController<CrossSigningSettingsViewState>() { ) : TypedEpoxyController<CrossSigningSettingsViewState>() {
interface InteractionListener { interface InteractionListener {
fun setupRecovery()
fun verifySession() fun verifySession()
} }
@ -68,6 +69,15 @@ class CrossSigningSettingsController @Inject constructor(
titleIconResourceId(R.drawable.ic_shield_black) titleIconResourceId(R.drawable.ic_shield_black)
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted))
} }
bottomSheetVerificationActionItem {
id("setup_recovery")
title(stringProvider.getString(R.string.settings_setup_secure_backup))
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
iconRes(R.drawable.ic_arrow_right)
listener {
interactionListener?.setupRecovery()
}
}
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("verify") id("verify")
title(stringProvider.getString(R.string.crosssigning_verify_this_session)) title(stringProvider.getString(R.string.crosssigning_verify_this_session))

View file

@ -53,6 +53,9 @@ class CrossSigningSettingsFragment @Inject constructor(
CrossSigningSettingsViewEvents.VerifySession -> { CrossSigningSettingsViewEvents.VerifySession -> {
navigator.waitSessionVerification(requireActivity()) navigator.waitSessionVerification(requireActivity())
} }
CrossSigningSettingsViewEvents.SetUpRecovery -> {
navigator.upgradeSessionSecurity(requireActivity(), false)
}
}.exhaustive }.exhaustive
} }
} }
@ -82,6 +85,10 @@ class CrossSigningSettingsFragment @Inject constructor(
super.onDestroyView() super.onDestroyView()
} }
override fun setupRecovery() {
viewModel.handle(CrossSigningSettingsAction.SetUpRecovery)
}
override fun verifySession() { override fun verifySession() {
viewModel.handle(CrossSigningSettingsAction.VerifySession) viewModel.handle(CrossSigningSettingsAction.VerifySession)
} }

View file

@ -24,5 +24,6 @@ import im.vector.riotx.core.platform.VectorViewEvents
sealed class CrossSigningSettingsViewEvents : VectorViewEvents { sealed class CrossSigningSettingsViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents()
object SetUpRecovery : CrossSigningSettingsViewEvents()
object VerifySession : CrossSigningSettingsViewEvents() object VerifySession : CrossSigningSettingsViewEvents()
} }

View file

@ -53,6 +53,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat
override fun handle(action: CrossSigningSettingsAction) { override fun handle(action: CrossSigningSettingsAction) {
when (action) { when (action) {
CrossSigningSettingsAction.SetUpRecovery -> {
_viewEvents.post(CrossSigningSettingsViewEvents.SetUpRecovery)
}
CrossSigningSettingsAction.VerifySession -> { CrossSigningSettingsAction.VerifySession -> {
_viewEvents.post(CrossSigningSettingsViewEvents.VerifySession) _viewEvents.post(CrossSigningSettingsViewEvents.VerifySession)
} }

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M1,2h21.5v5h-21.5zM1,17.7h21.5v5h-21.5z"/>
<path
android:pathData="M3.1663,12.4014C3.1663,7.6467 6.9953,3.8177 11.75,3.8177C13.0964,3.8177 14.4429,4.1544 15.6631,4.7435H14.9899C14.5691,4.7435 14.2325,5.0801 14.2325,5.5008C14.2325,5.9216 14.5691,6.2582 14.9899,6.2582H17.3041C17.809,6.2582 18.1877,5.8375 18.1877,5.3746V3.0604C18.1877,2.6396 17.8511,2.303 17.4303,2.303C17.0096,2.303 16.673,2.6396 16.673,3.0604V3.6074C16.6309,3.5653 16.5888,3.5653 16.5467,3.5232C15.074,2.7238 13.433,2.303 11.75,2.303C6.1958,2.303 1.6515,6.8473 1.6515,12.4014C1.6515,14.0845 2.0723,15.7676 2.8717,17.2403C2.998,17.4928 3.2504,17.619 3.545,17.619C3.6712,17.619 3.7974,17.5769 3.9236,17.5348C4.3023,17.3245 4.4286,16.8616 4.2182,16.525C3.5029,15.2627 3.1663,13.8321 3.1663,12.4014Z"
android:fillColor="#2E2F32"/>
<path
android:pathData="M20.6281,7.5626C20.4177,7.1839 19.9548,7.0577 19.6182,7.2681C19.2395,7.4785 19.1133,7.9413 19.3237,8.2779C19.9969,9.5402 20.3756,10.9288 20.3756,12.4015C20.3756,17.1562 16.5045,20.9852 11.7919,20.9852C10.4454,20.9852 9.099,20.6486 7.8787,20.0595H8.552C8.9727,20.0595 9.3094,19.7229 9.3094,19.3021C9.3094,18.8813 8.9727,18.5447 8.552,18.5447H6.2377C5.7328,18.5447 5.3541,18.9655 5.3541,19.4283V21.7426C5.3541,22.1633 5.6908,22.4999 6.1115,22.4999C6.5323,22.4999 6.8689,22.1633 6.8689,21.7426V21.1956C6.911,21.2376 6.9531,21.2376 6.9951,21.2797C8.4257,22.0792 10.0667,22.4999 11.7498,22.4999C17.304,22.4999 21.8483,17.9556 21.8483,12.4015C21.8483,10.7184 21.4275,9.0353 20.6281,7.5626Z"
android:fillColor="#2E2F32"/>
</group>
<path
android:pathData="M3,9C1.8954,9 1,9.8954 1,11V14C1,15.1046 1.8954,16 3,16H21C22.1046,16 23,15.1046 23,14V11C23,9.8954 22.1046,9 21,9H3ZM5.25,10.5C4.8358,10.5 4.5,10.8358 4.5,11.25C4.5,11.6642 4.8358,12 5.25,12H7.75C8.1642,12 8.5,11.6642 8.5,11.25C8.5,10.8358 8.1642,10.5 7.75,10.5H5.25ZM9.5,11.25C9.5,10.8358 9.8358,10.5 10.25,10.5H10.75C11.1642,10.5 11.5,10.8358 11.5,11.25C11.5,11.6642 11.1642,12 10.75,12H10.25C9.8358,12 9.5,11.6642 9.5,11.25ZM13.25,10.5C12.8358,10.5 12.5,10.8358 12.5,11.25C12.5,11.6642 12.8358,12 13.25,12H15.75C16.1642,12 16.5,11.6642 16.5,11.25C16.5,10.8358 16.1642,10.5 15.75,10.5H13.25ZM17.5,11.25C17.5,10.8358 17.8358,10.5 18.25,10.5H18.75C19.1642,10.5 19.5,10.8358 19.5,11.25C19.5,11.6642 19.1642,12 18.75,12H18.25C17.8358,12 17.5,11.6642 17.5,11.25ZM5.25,13C4.8358,13 4.5,13.3358 4.5,13.75C4.5,14.1642 4.8358,14.5 5.25,14.5H5.75C6.1642,14.5 6.5,14.1642 6.5,13.75C6.5,13.3358 6.1642,13 5.75,13H5.25ZM7.5,13.75C7.5,13.3358 7.8358,13 8.25,13H10.75C11.1642,13 11.5,13.3358 11.5,13.75C11.5,14.1642 11.1642,14.5 10.75,14.5H8.25C7.8358,14.5 7.5,14.1642 7.5,13.75ZM13.25,13C12.8358,13 12.5,13.3358 12.5,13.75C12.5,14.1642 12.8358,14.5 13.25,14.5H13.75C14.1642,14.5 14.5,14.1642 14.5,13.75C14.5,13.3358 14.1642,13 13.75,13H13.25Z"
android:fillColor="#2E2F32"
android:fillType="evenOdd"/>
</vector>

View file

@ -29,7 +29,7 @@
android:layout_height="32dp" android:layout_height="32dp"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_message_key" /> android:src="@drawable/ic_secure_backup_24dp" />
<TextView <TextView
android:id="@+id/bootstrapTitleText" android:id="@+id/bootstrapTitleText"

View file

@ -15,10 +15,9 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="@string/bootstrap_save_key_description" android:text="@string/bottom_sheet_save_your_recovery_key_content"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="14sp" android:textSize="14sp" />
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/bootstrapRecoveryKeyText" android:id="@+id/bootstrapRecoveryKeyText"
@ -44,7 +43,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="50dp" android:minHeight="50dp"
app:actionTitle="@string/copy_value" app:actionTitle="@string/action_copy"
app:leftIcon="@drawable/ic_clipboard" app:leftIcon="@drawable/ic_clipboard"
app:rightIcon="@drawable/ic_arrow_right" app:rightIcon="@drawable/ic_arrow_right"
app:tint="?colorAccent" /> app:tint="?colorAccent" />

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="200dp"
android:orientation="vertical">
<TextView
android:id="@+id/bootstrapSetupSecureText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/bottom_sheet_setup_secure_backup_content_1"
android:textColor="?riotx_text_primary"
android:textSize="14sp" />
<TextView
android:id="@+id/bootstrapSetupSecureText2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/bottom_sheet_setup_secure_backup_content_2"
android:textColor="?riotx_text_primary"
android:textSize="14sp"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:background="?attr/vctr_list_divider_color" />
<im.vector.riotx.core.ui.views.BottomSheetActionButton
android:id="@+id/bootstrapSetupSecureSubmit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp"
app:actionTitle="@string/bottom_sheet_setup_secure_backup_submit"
app:rightIcon="@drawable/ic_arrow_right"
app:tint="?colorAccent" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/vctr_list_divider_color" />
</LinearLayout>

View file

@ -19,7 +19,7 @@
app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_key" app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_key"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_key" app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_key"
android:src="@drawable/ic_message_key" /> android:src="@drawable/ic_secure_backup_24dp" />
<TextView <TextView
android:id="@+id/ssss_restore_with_key" android:id="@+id/ssss_restore_with_key"

View file

@ -19,7 +19,7 @@
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" /> android:src="@drawable/ic_secure_backup_24dp" />
<TextView <TextView
android:id="@+id/ssss_restore_with_passphrase" android:id="@+id/ssss_restore_with_passphrase"
@ -98,7 +98,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/use_recovery_key" android:text="@string/use_recovery_key"
app:icon="@drawable/ic_message_key" app:icon="@drawable/ic_secure_backup_24dp"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View file

@ -117,6 +117,7 @@
<string name="action_mark_room_read">Mark as read</string> <string name="action_mark_room_read">Mark as read</string>
<string name="action_open">Open</string> <string name="action_open">Open</string>
<string name="action_close">Close</string> <string name="action_close">Close</string>
<string name="action_copy">Copy</string>
<string name="copied_to_clipboard">Copied to clipboard</string> <string name="copied_to_clipboard">Copied to clipboard</string>
<string name="disable">Disable</string> <string name="disable">Disable</string>
@ -2478,4 +2479,14 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="a11y_stop_camera">Stop the camera</string> <string name="a11y_stop_camera">Stop the camera</string>
<string name="a11y_start_camera">Start the camera</string> <string name="a11y_start_camera">Start the camera</string>
<string name="settings_setup_secure_backup">Set up Secure Backup</string>
<string name="bottom_sheet_setup_secure_backup_title">Set up Secure Backup</string>
<string name="bottom_sheet_setup_secure_backup_content_1">Backup your encryption keys with your account data in case you lose access to your logins.</string>
<string name="bottom_sheet_setup_secure_backup_content_2">Your keys will be secured with a unique Recovery Key.</string>
<string name="bottom_sheet_setup_secure_backup_submit">Set up</string>
<string name="bottom_sheet_save_your_recovery_key_title">Save your Recovery Key</string>
<string name="bottom_sheet_save_your_recovery_key_content">Store your Recovery Key somewhere safe. It can be used to unlock your encrypted messages and data.</string>
</resources> </resources>