mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
4S Activity WIP
This commit is contained in:
parent
3dc89c8d87
commit
cb669ad881
17 changed files with 622 additions and 20 deletions
|
@ -19,7 +19,6 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
<activity
|
||||
android:name=".features.MainActivity"
|
||||
android:theme="@style/AppTheme.Launcher" />
|
||||
|
@ -57,7 +56,6 @@
|
|||
<activity
|
||||
android:name=".features.crypto.keysbackup.settings.KeysBackupManageActivity"
|
||||
android:label="@string/encryption_message_recovery" />
|
||||
|
||||
<activity
|
||||
android:name=".features.reactions.EmojiReactionPickerActivity"
|
||||
android:label="@string/title_activity_emoji_reaction_picker" />
|
||||
|
@ -73,7 +71,7 @@
|
|||
android:value=".features.home.HomeActivity" />
|
||||
</activity>
|
||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||
<activity android:name="im.vector.riotx.features.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name=".features.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||
<activity android:name=".features.link.LinkHandlerActivity">
|
||||
<intent-filter>
|
||||
|
@ -97,6 +95,7 @@
|
|||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
@ -104,15 +103,14 @@
|
|||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
||||
|
||||
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
||||
<activity
|
||||
android:name=".features.signout.soft.SoftLogoutActivity"
|
||||
|
@ -129,7 +127,6 @@
|
|||
<data android:host="matrix.to" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
||||
android:parentActivityName=".features.home.HomeActivity">
|
||||
|
@ -137,9 +134,9 @@
|
|||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".features.home.HomeActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.qrcode.QrCodeScannerActivity" />
|
||||
|
||||
<activity android:name=".features.crypto.quads.SharedSecureStorageActivity" />
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
|
@ -149,25 +146,17 @@
|
|||
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
<service
|
||||
android:name=".core.services.CallService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".core.services.VectorSyncService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Receivers -->
|
||||
|
||||
android:exported="false" /> <!-- Receivers -->
|
||||
<!-- Exported false, should only be accessible from this app!! -->
|
||||
<receiver
|
||||
android:name=".features.notifications.NotificationBroadcastReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Providers -->
|
||||
|
||||
android:exported="false" /> <!-- Providers -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.riotx.core.preference.UserAvatarPreference
|
|||
import im.vector.riotx.features.MainActivity
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity
|
||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.riotx.features.debug.DebugMenuActivity
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
|
@ -151,6 +152,8 @@ interface ScreenComponent {
|
|||
|
||||
fun inject(deviceListBottomSheet: DeviceListBottomSheet)
|
||||
|
||||
fun inject(activity: SharedSecureStorageActivity)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(vectorComponent: VectorComponent,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 im.vector.riotx.core.platform.VectorViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
import im.vector.riotx.core.platform.WaitingViewData
|
||||
|
||||
sealed class SharedSecureStorageAction : VectorViewModelAction {
|
||||
|
||||
object TogglePasswordVisibility : SharedSecureStorageAction()
|
||||
object Cancel : SharedSecureStorageAction()
|
||||
data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction()
|
||||
}
|
||||
|
||||
sealed class SharedSecureStorageViewEvent : VectorViewEvents {
|
||||
|
||||
object Dismiss : SharedSecureStorageViewEvent()
|
||||
data class Error(val message: String) : SharedSecureStorageViewEvent()
|
||||
data class InlineError(val message: String) : SharedSecureStorageViewEvent()
|
||||
object ShowModalLoading : SharedSecureStorageViewEvent()
|
||||
object HideModalLoading : SharedSecureStorageViewEvent()
|
||||
data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent()
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
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.platform.SimpleFragmentActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
||||
|
||||
@Parcelize
|
||||
data class Args(
|
||||
val keyId: String?,
|
||||
val requestedSecrets: List<String>,
|
||||
val resultKeyStoreAlias: String
|
||||
) : Parcelable
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
private fun Disposable.disposeOnDestroyView(): Disposable {
|
||||
uiDisposables.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
private val viewModel: SharedSecureStorageViewModel by viewModel()
|
||||
@Inject lateinit var viewModelFactory: SharedSecureStorageViewModel.Factory
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
super.injectWith(injector)
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
toolbar.visibility = View.GONE
|
||||
if (isFirstCreation()) {
|
||||
addFragment(R.id.container, SharedSecuredStoragePassphraseFragment::class.java)
|
||||
}
|
||||
|
||||
|
||||
viewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
observeViewEvents(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
viewModel.subscribe(this) {
|
||||
// renderState(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeViewEvents(it: SharedSecureStorageViewEvent?) {
|
||||
when (it) {
|
||||
is SharedSecureStorageViewEvent.Dismiss -> {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
is SharedSecureStorageViewEvent.ShowModalLoading -> {
|
||||
showWaitingView()
|
||||
}
|
||||
is SharedSecureStorageViewEvent.HideModalLoading -> {
|
||||
hideWaitingView()
|
||||
}
|
||||
is SharedSecureStorageViewEvent.UpdateLoadingState -> {
|
||||
updateWaitingView(it.waitingData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fun renderState(state: SharedSecureStorageViewState) {
|
||||
// }
|
||||
|
||||
companion object {
|
||||
|
||||
const val RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
|
||||
fun newIntent(context: Context, keyId: String? = null, requestedSecrets: List<String>, resultKeyStoreAlias: String = RESULT_KEYSTORE_ALIAS): Intent {
|
||||
require(requestedSecrets.isNotEmpty())
|
||||
return Intent(context, SharedSecureStorageActivity::class.java).also {
|
||||
it.putExtra(MvRx.KEY_ARG, Args(
|
||||
keyId,
|
||||
requestedSecrets,
|
||||
resultKeyStoreAlias
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
||||
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.platform.WaitingViewData
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
|
||||
data class SharedSecureStorageViewState(
|
||||
val requestedSecrets: List<String> = emptyList(),
|
||||
val passphraseVisible: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
class SharedSecureStorageViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: SharedSecureStorageViewState,
|
||||
private val stringProvider: StringProvider,
|
||||
private val session: Session)
|
||||
: VectorViewModel<SharedSecureStorageViewState, SharedSecureStorageAction, SharedSecureStorageViewEvent>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: SharedSecureStorageViewState): SharedSecureStorageViewModel
|
||||
}
|
||||
|
||||
override fun handle(action: SharedSecureStorageAction) = withState { state ->
|
||||
when (action) {
|
||||
is SharedSecureStorageAction.TogglePasswordVisibility -> {
|
||||
setState {
|
||||
copy(
|
||||
passphraseVisible = !passphraseVisible
|
||||
)
|
||||
}
|
||||
}
|
||||
is SharedSecureStorageAction.Cancel -> {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
|
||||
}
|
||||
is SharedSecureStorageAction.SubmitPassphrase -> {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
|
||||
val passphrase = action.passphrase
|
||||
val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
|
||||
if (!keyInfoResult.isSuccess()) {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key"))
|
||||
return@withState
|
||||
}
|
||||
val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo
|
||||
|
||||
// TODO
|
||||
// val decryptedSecretMap = HashMap<String, String>()
|
||||
// val errors = ArrayList<Throwable>()
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
||||
WaitingViewData(
|
||||
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
||||
isIndeterminate = true
|
||||
)
|
||||
))
|
||||
state.requestedSecrets.forEach {
|
||||
val keySpec = Curve25519AesSha2KeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
keyInfo.content.passphrase?.salt ?: "",
|
||||
keyInfo.content.passphrase?.iterations ?: 0,
|
||||
// TODO
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
||||
WaitingViewData(
|
||||
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
||||
isIndeterminate = false,
|
||||
progress = progress,
|
||||
progressTotal = total
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
session.sharedSecretStorageService.getSecret(
|
||||
name = it,
|
||||
keyId = keyInfo.id,
|
||||
secretKey = keySpec,
|
||||
callback = object : MatrixCallback<String> {
|
||||
override fun onSuccess(data: String) {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.InlineError(failure.localizedMessage))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SharedSecureStorageViewModel, SharedSecureStorageViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? {
|
||||
val activity: SharedSecureStorageActivity = viewModelContext.activity()
|
||||
val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG)
|
||||
return activity.viewModelFactory.create(
|
||||
SharedSecureStorageViewState(
|
||||
requestedSecrets = args.requestedSecrets
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
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.utils.DebouncedClickListener
|
||||
import me.gujun.android.span.span
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase
|
||||
|
||||
val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
|
||||
|
||||
@BindView(R.id.ssss_restore_with_passphrase_warning_text)
|
||||
lateinit var warningText: TextView
|
||||
|
||||
@BindView(R.id.ssss_restore_with_passphrase_warning_reason)
|
||||
lateinit var reasonText: TextView
|
||||
|
||||
@BindView(R.id.ssss_passphrase_enter_til)
|
||||
lateinit var mPassphraseInputLayout: TextInputLayout
|
||||
|
||||
@BindView(R.id.ssss_passphrase_enter_edittext)
|
||||
lateinit var mPassphraseTextEdit: EditText
|
||||
|
||||
@BindView(R.id.ssss_view_show_password)
|
||||
lateinit var mPassphraseReveal: ImageView
|
||||
|
||||
@BindView(R.id.ssss_passphrase_submit)
|
||||
lateinit var submitButton: Button
|
||||
|
||||
@BindView(R.id.ssss_passphrase_cancel)
|
||||
lateinit var cancelButton: Button
|
||||
|
||||
@OnClick(R.id.ssss_view_show_password)
|
||||
fun toggleVisibilityMode() {
|
||||
sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
warningText.text = span {
|
||||
span(getString(R.string.enter_secret_storage_passphrase_warning)) {
|
||||
textStyle = "bold"
|
||||
}
|
||||
+" "
|
||||
+getString(R.string.enter_secret_storage_passphrase_warning_text)
|
||||
}
|
||||
|
||||
reasonText.text = getString(R.string.enter_secret_storage_passphrase_reason_verify)
|
||||
|
||||
mPassphraseTextEdit.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
mPassphraseTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
submit()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
mPassphraseTextEdit.textChanges()
|
||||
.subscribe {
|
||||
mPassphraseInputLayout.error = null
|
||||
submitButton.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SharedSecureStorageViewEvent.InlineError -> {
|
||||
mPassphraseInputLayout.error = it.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitButton.setOnClickListener(DebouncedClickListener(
|
||||
View.OnClickListener {
|
||||
submit()
|
||||
}
|
||||
))
|
||||
|
||||
cancelButton.setOnClickListener(DebouncedClickListener(
|
||||
View.OnClickListener {
|
||||
sharedViewModel.handle(SharedSecureStorageAction.Cancel)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
val text = mPassphraseTextEdit.text.toString()
|
||||
if (text.isBlank()) return // Should not reach this point as button disabled
|
||||
submitButton.isEnabled = false
|
||||
sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text))
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||
val shouldBeVisible = state.passphraseVisible
|
||||
mPassphraseTextEdit.showPassword(shouldBeVisible)
|
||||
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
||||
}
|
||||
}
|
|
@ -29,4 +29,5 @@ sealed class VerificationAction : VectorViewModelAction {
|
|||
data class SASDoNotMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction()
|
||||
object GotItConclusion : VerificationAction()
|
||||
object SkipVerification : VerificationAction()
|
||||
object VerifyFromPassphrase : VerificationAction()
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.riotx.core.di.ScreenComponent
|
|||
import im.vector.riotx.core.extensions.commitTransactionNow
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity
|
||||
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
|
||||
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
||||
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
|
||||
|
@ -87,6 +88,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||
startActivity(SharedSecureStorageActivity.newIntent(requireContext(),null, listOf("m.cross_signing.user_signing")))
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,4 +23,5 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
|||
*/
|
||||
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
||||
object Dismiss : VerificationBottomSheetViewEvents()
|
||||
object AccessSecretStore : VerificationBottomSheetViewEvents()
|
||||
}
|
||||
|
|
|
@ -253,6 +253,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
is VerificationAction.SkipVerification -> {
|
||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||
}
|
||||
is VerificationAction.VerifyFromPassphrase -> {
|
||||
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ class VerificationRequestFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onClickRecoverFromPassphrase() {
|
||||
viewModel.handle(VerificationAction.VerifyFromPassphrase)
|
||||
}
|
||||
|
||||
override fun onClickDismiss() {
|
||||
|
|
|
@ -148,9 +148,22 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
|
||||
// We need to ask
|
||||
sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
|
||||
navigator.waitSessionVerification(this)
|
||||
} else {
|
||||
// TODO upgrade security -> bootstrap cross signing
|
||||
PopupAlertManager.postVectorAlert(
|
||||
PopupAlertManager.VectorAlert(
|
||||
uid = "completeSecurity",
|
||||
title = getString(R.string.new_signin),
|
||||
description = getString(R.string.complete_security),
|
||||
iconId = R.drawable.ic_shield_warning
|
||||
).apply {
|
||||
colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_destructive_accent)
|
||||
contentAction = Runnable {
|
||||
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
|
||||
it.navigator.waitSessionVerification(it)
|
||||
}
|
||||
}
|
||||
dismissedAction = Runnable {}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<?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="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:src="@drawable/key_big"
|
||||
android:tint="?riotx_text_primary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_passphrase"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_passphrase" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ssss_restore_with_passphrase"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/enter_secret_storage_passphrase"
|
||||
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_passphrase_warning_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_passphrase"
|
||||
tools:text="@string/enter_secret_storage_passphrase_warning_text" />
|
||||
|
||||
|
||||
<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
|
||||
android:id="@+id/ssss_passphrase_enter_til"
|
||||
style="@style/VectorTextInputLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/ssss_view_show_password"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase_warning_reason">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/ssss_passphrase_enter_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_enter_passphrase"
|
||||
android:imeOptions="actionDone"
|
||||
android:maxLines="3"
|
||||
android:singleLine="false"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ssss_view_show_password"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_eye_black"
|
||||
android:tint="?colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+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_submit"
|
||||
style="@style/VectorButtonStylePositive"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||
android:minWidth="200dp"
|
||||
android:text="@string/_continue"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/ssss_passphrase_cancel"
|
||||
style="@style/VectorButtonStyleDestructive"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||
android:minWidth="200dp"
|
||||
android:text="@string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_submit" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -32,5 +32,6 @@
|
|||
|
||||
<!-- Max width for some buttons -->
|
||||
<dimen name="button_max_width">280dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -2151,4 +2151,5 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
|||
<string name="qr_code_scanned_by_other_no">No</string>
|
||||
|
||||
<string name="no_connectivity_to_the_server_indicator">Connectivity to the server has been lost</string>
|
||||
<string name="title_activity_share_secure_storage">ShareSecureStorageActivity</string>
|
||||
</resources>
|
||||
|
|
|
@ -21,6 +21,15 @@
|
|||
|
||||
<string name="verification_cannot_access_other_session">Can‘t access an existing session?</string>
|
||||
<string name="verification_use_passphrase">Use your recovery key or passphrase</string>
|
||||
|
||||
|
||||
<string name="new_signin">New Sign In</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_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>
|
||||
|
||||
<!-- END Strings added by Valere -->
|
||||
|
||||
|
||||
|
|
11
vector/src/main/res/values/styles.xml
Normal file
11
vector/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme.Light.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Light.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.Light.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
</resources>
|
Loading…
Reference in a new issue