4S Activity WIP
This commit is contained in:
parent
3dc89c8d87
commit
cb669ad881
|
@ -19,7 +19,6 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme.Light"
|
android:theme="@style/AppTheme.Light"
|
||||||
tools:replace="android:allowBackup">
|
tools:replace="android:allowBackup">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.MainActivity"
|
android:name=".features.MainActivity"
|
||||||
android:theme="@style/AppTheme.Launcher" />
|
android:theme="@style/AppTheme.Launcher" />
|
||||||
|
@ -57,7 +56,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.crypto.keysbackup.settings.KeysBackupManageActivity"
|
android:name=".features.crypto.keysbackup.settings.KeysBackupManageActivity"
|
||||||
android:label="@string/encryption_message_recovery" />
|
android:label="@string/encryption_message_recovery" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.reactions.EmojiReactionPickerActivity"
|
android:name=".features.reactions.EmojiReactionPickerActivity"
|
||||||
android:label="@string/title_activity_emoji_reaction_picker" />
|
android:label="@string/title_activity_emoji_reaction_picker" />
|
||||||
|
@ -73,7 +71,7 @@
|
||||||
android:value=".features.home.HomeActivity" />
|
android:value=".features.home.HomeActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
<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.webview.VectorWebViewActivity" />
|
||||||
<activity android:name=".features.link.LinkHandlerActivity">
|
<activity android:name=".features.link.LinkHandlerActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -97,6 +95,7 @@
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
@ -104,15 +103,14 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.OPENABLE" />
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
||||||
|
|
||||||
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.signout.soft.SoftLogoutActivity"
|
android:name=".features.signout.soft.SoftLogoutActivity"
|
||||||
|
@ -129,7 +127,6 @@
|
||||||
<data android:host="matrix.to" />
|
<data android:host="matrix.to" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
||||||
android:parentActivityName=".features.home.HomeActivity">
|
android:parentActivityName=".features.home.HomeActivity">
|
||||||
|
@ -137,9 +134,9 @@
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".features.home.HomeActivity" />
|
android:value=".features.home.HomeActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".features.qrcode.QrCodeScannerActivity" />
|
<activity android:name=".features.qrcode.QrCodeScannerActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".features.crypto.quads.SharedSecureStorageActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.yalantis.ucrop.UCropActivity"
|
android:name="com.yalantis.ucrop.UCropActivity"
|
||||||
android:screenOrientation="portrait" />
|
android:screenOrientation="portrait" />
|
||||||
|
@ -149,25 +146,17 @@
|
||||||
android:theme="@style/AppTheme.AttachmentsPreview" />
|
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.CallService"
|
android:name=".core.services.CallService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.VectorSyncService"
|
android:name=".core.services.VectorSyncService"
|
||||||
android:exported="false" />
|
android:exported="false" /> <!-- Receivers -->
|
||||||
|
|
||||||
<!-- Receivers -->
|
|
||||||
|
|
||||||
<!-- Exported false, should only be accessible from this app!! -->
|
<!-- Exported false, should only be accessible from this app!! -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".features.notifications.NotificationBroadcastReceiver"
|
android:name=".features.notifications.NotificationBroadcastReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" /> <!-- Providers -->
|
||||||
|
|
||||||
<!-- Providers -->
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.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.MainActivity
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
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.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.riotx.features.debug.DebugMenuActivity
|
import im.vector.riotx.features.debug.DebugMenuActivity
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
|
@ -151,6 +152,8 @@ interface ScreenComponent {
|
||||||
|
|
||||||
fun inject(deviceListBottomSheet: DeviceListBottomSheet)
|
fun inject(deviceListBottomSheet: DeviceListBottomSheet)
|
||||||
|
|
||||||
|
fun inject(activity: SharedSecureStorageActivity)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
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()
|
data class SASDoNotMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction()
|
||||||
object GotItConclusion : VerificationAction()
|
object GotItConclusion : VerificationAction()
|
||||||
object SkipVerification : 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.commitTransactionNow
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
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.choose.VerificationChooseMethodFragment
|
||||||
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
||||||
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
|
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
|
||||||
|
@ -87,6 +88,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||||
|
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||||
|
startActivity(SharedSecureStorageActivity.newIntent(requireContext(),null, listOf("m.cross_signing.user_signing")))
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,5 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
*/
|
*/
|
||||||
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
||||||
object Dismiss : VerificationBottomSheetViewEvents()
|
object Dismiss : VerificationBottomSheetViewEvents()
|
||||||
|
object AccessSecretStore : VerificationBottomSheetViewEvents()
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,6 +253,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||||
is VerificationAction.SkipVerification -> {
|
is VerificationAction.SkipVerification -> {
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
}
|
}
|
||||||
|
is VerificationAction.VerifyFromPassphrase -> {
|
||||||
|
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ class VerificationRequestFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClickRecoverFromPassphrase() {
|
override fun onClickRecoverFromPassphrase() {
|
||||||
|
viewModel.handle(VerificationAction.VerifyFromPassphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClickDismiss() {
|
override fun onClickDismiss() {
|
||||||
|
|
|
@ -148,9 +148,22 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
// We need to ask
|
// We need to ask
|
||||||
sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
|
sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
|
||||||
navigator.waitSessionVerification(this)
|
PopupAlertManager.postVectorAlert(
|
||||||
} else {
|
PopupAlertManager.VectorAlert(
|
||||||
// TODO upgrade security -> bootstrap cross signing
|
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 -->
|
<!-- Max width for some buttons -->
|
||||||
<dimen name="button_max_width">280dp</dimen>
|
<dimen name="button_max_width">280dp</dimen>
|
||||||
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</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="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="no_connectivity_to_the_server_indicator">Connectivity to the server has been lost</string>
|
||||||
|
<string name="title_activity_share_secure_storage">ShareSecureStorageActivity</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -21,6 +21,15 @@
|
||||||
|
|
||||||
<string name="verification_cannot_access_other_session">Can‘t access an existing session?</string>
|
<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="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 -->
|
<!-- END Strings added by Valere -->
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 New Issue