Merge pull request #3445 from vector-im/feature/bma/withContext

Migration to coroutines
This commit is contained in:
Benoit Marty 2021-06-09 17:58:08 +02:00 committed by GitHub
commit 2864101e2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 187 additions and 275 deletions

View File

@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
import kotlin.jvm.Throws
interface CryptoService {
@ -82,9 +81,11 @@ interface CryptoService {
fun getDeviceTrackingStatus(userId: String): Int
fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
password: String,
progressListener: ProgressListener?): ImportRoomKeysResult
fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>)
suspend fun exportRoomKeys(password: String): ByteArray
fun setRoomBlacklistUnverifiedDevices(roomId: String)

View File

@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor(
* Export the crypto keys
*
* @param password the password
* @param callback the exported keys
* @return the exported keys
*/
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}.foldToCallback(callback)
}
override suspend fun exportRoomKeys(password: String): ByteArray {
return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}
/**
@ -963,42 +959,37 @@ internal class DefaultCryptoService @Inject constructor(
* @param roomKeysAsArray the room keys as array.
* @param password the password
* @param progressListener the progress listener
* @param callback the asynchronous callback.
* @return the result ImportRoomKeysResult
*/
override fun importRoomKeys(roomKeysAsArray: ByteArray,
password: String,
progressListener: ProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
Timber.v("## CRYPTO | importRoomKeys starts")
override suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
password: String,
progressListener: ProgressListener?): ImportRoomKeysResult {
return withContext(coroutineDispatchers.crypto) {
Timber.v("## CRYPTO | importRoomKeys starts")
val t0 = System.currentTimeMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis()
val t0 = System.currentTimeMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis()
Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
val importedSessions = MoshiProvider.providesMoshi()
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
.fromJson(roomKeys)
val importedSessions = MoshiProvider.providesMoshi()
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
.fromJson(roomKeys)
val t2 = System.currentTimeMillis()
val t2 = System.currentTimeMillis()
Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
if (importedSessions == null) {
throw Exception("Error")
}
if (importedSessions == null) {
throw Exception("Error")
}
megolmSessionDataImporter.handle(
megolmSessionsData = importedSessions,
fromBackup = false,
progressListener = progressListener
)
}
}.foldToCallback(callback)
megolmSessionDataImporter.handle(
megolmSessionsData = importedSessions,
fromBackup = false,
progressListener = progressListener
)
}
}

View File

@ -24,7 +24,6 @@ import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
@ -168,9 +167,7 @@ internal class DefaultTimeline(
timelineEvents.addChangeListener(eventsChangeListener)
handleInitialLoad()
loadRoomMembersTask
.configureWith(LoadRoomMembersTask.Params(roomId)) {
this.callback = NoOpMatrixCallback()
}
.configureWith(LoadRoomMembersTask.Params(roomId))
.executeBy(taskExecutor)
// Ensure ReadReceipt from init sync are loaded

View File

@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.session.room.typing
import android.os.SystemClock
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import org.matrix.android.sdk.api.MatrixCallback
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import timber.log.Timber
/**
@ -35,7 +35,6 @@ import timber.log.Timber
*/
internal class DefaultTypingService @AssistedInject constructor(
@Assisted private val roomId: String,
private val taskExecutor: TaskExecutor,
private val sendTypingTask: SendTypingTask
) : TypingService {
@ -44,8 +43,8 @@ internal class DefaultTypingService @AssistedInject constructor(
fun create(roomId: String): DefaultTypingService
}
private var currentTask: Cancelable? = null
private var currentAutoStopTask: Cancelable? = null
private val coroutineScope = CoroutineScope(Job())
private var currentTask: Job? = null
// What the homeserver knows
private var userIsTyping = false
@ -53,26 +52,24 @@ internal class DefaultTypingService @AssistedInject constructor(
// Last time the user is typing event has been sent
private var lastRequestTimestamp: Long = 0
/**
* Notify to the server that the user is typing and schedule the auto typing off
*/
override fun userIsTyping() {
scheduleAutoStop()
val now = SystemClock.elapsedRealtime()
if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) {
Timber.d("Typing: Skip start request")
return
}
Timber.d("Typing: Send start request")
userIsTyping = true
lastRequestTimestamp = now
currentTask?.cancel()
val params = SendTypingTask.Params(roomId, true)
currentTask = sendTypingTask
.configureWith(params)
.executeBy(taskExecutor)
currentTask = coroutineScope.launch {
if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) {
Timber.d("Typing: Skip start request")
} else {
Timber.d("Typing: Send start request")
lastRequestTimestamp = now
sendRequest(true)
}
delay(MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS)
Timber.d("Typing: auto stop")
sendRequest(false)
}
}
override fun userStopsTyping() {
@ -82,35 +79,22 @@ internal class DefaultTypingService @AssistedInject constructor(
}
Timber.d("Typing: Send stop request")
userIsTyping = false
lastRequestTimestamp = 0
currentAutoStopTask?.cancel()
currentTask?.cancel()
val params = SendTypingTask.Params(roomId, false)
currentTask = sendTypingTask
.configureWith(params)
.executeBy(taskExecutor)
currentTask = coroutineScope.launch {
sendRequest(false)
}
}
private fun scheduleAutoStop() {
Timber.d("Typing: Schedule auto stop")
currentAutoStopTask?.cancel()
val params = SendTypingTask.Params(
roomId,
false,
delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS)
currentAutoStopTask = sendTypingTask
.configureWith(params) {
callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
userIsTyping = false
}
}
}
.executeBy(taskExecutor)
private suspend fun sendRequest(isTyping: Boolean) {
try {
sendTypingTask.execute(SendTypingTask.Params(roomId, isTyping))
userIsTyping = isTyping
} catch (failure: Throwable) {
// Ignore network error, etc...
Timber.w(failure, "Unable to send typing request")
}
}
companion object {

View File

@ -17,11 +17,10 @@
package org.matrix.android.sdk.internal.session.room.typing
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import kotlinx.coroutines.delay
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import javax.inject.Inject
internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
@ -29,9 +28,7 @@ internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
data class Params(
val roomId: String,
val isTyping: Boolean,
val typingTimeoutMillis: Int? = 30_000,
// Optional delay before sending the request to the homeserver
val delay: Long? = null
val typingTimeoutMillis: Int? = 30_000
)
}
@ -42,8 +39,6 @@ internal class DefaultSendTypingTask @Inject constructor(
) : SendTypingTask {
override suspend fun execute(params: SendTypingTask.Params) {
delay(params.delay ?: -1)
executeRequest(globalErrorReceiver) {
roomAPI.sendTypingState(
params.roomId,

View File

@ -0,0 +1 @@
Migrate DefaultTypingService, KeysImporter and KeysExporter to coroutines

View File

@ -32,6 +32,7 @@ import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.verification.VerificationBottomSheet
@ -138,6 +139,7 @@ interface ScreenComponent {
fun inject(activity: LinkHandlerActivity)
fun inject(activity: MainActivity)
fun inject(activity: RoomDirectoryActivity)
fun inject(activity: KeysBackupSetupActivity)
fun inject(activity: BugReportActivity)
fun inject(activity: FilteredRoomsActivity)
fun inject(activity: CreateRoomActivity)

View File

@ -18,35 +18,24 @@ package im.vector.app.features.crypto.keys
import android.content.Context
import android.net.Uri
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.util.awaitCallback
class KeysExporter(private val session: Session) {
import javax.inject.Inject
class KeysExporter @Inject constructor(
private val session: Session,
private val context: Context
) {
/**
* Export keys and return the file path with the callback
* Export keys and write them to the provided uri
*/
fun export(context: Context, password: String, uri: Uri, callback: MatrixCallback<Boolean>) {
session.coroutineScope.launch(Dispatchers.Main) {
runCatching {
withContext(Dispatchers.IO) {
val data = awaitCallback<ByteArray> { session.cryptoService().exportRoomKeys(password, it) }
val os = context.contentResolver?.openOutputStream(uri)
if (os == null) {
false
} else {
os.write(data)
os.flush()
true
}
}
}.foldToCallback(callback)
suspend fun export(password: String, uri: Uri) {
return withContext(Dispatchers.IO) {
val data = session.cryptoService().exportRoomKeys(password)
context.contentResolver.openOutputStream(uri)
?.use { it.write(data) }
?: throw IllegalStateException("Unable to open file for writting")
}
}
}

View File

@ -20,49 +20,27 @@ import android.content.Context
import android.net.Uri
import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.resources.openResource
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
class KeysImporter(private val session: Session) {
import javax.inject.Inject
class KeysImporter @Inject constructor(
private val context: Context,
private val session: Session
) {
/**
* Import keys from provided Uri
*/
fun import(context: Context,
uri: Uri,
mimetype: String?,
password: String,
callback: MatrixCallback<ImportRoomKeysResult>) {
session.coroutineScope.launch(Dispatchers.Main) {
runCatching {
withContext(Dispatchers.IO) {
val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri))
if (resource?.mContentStream == null) {
throw Exception("Error")
}
val data: ByteArray
try {
data = resource.mContentStream!!.use { it.readBytes() }
} catch (e: Exception) {
Timber.e(e, "## importKeys()")
throw e
}
awaitCallback<ImportRoomKeysResult> {
session.cryptoService().importRoomKeys(data, password, null, it)
}
}
}.foldToCallback(callback)
suspend fun import(uri: Uri,
mimetype: String?,
password: String): ImportRoomKeysResult {
return withContext(Dispatchers.IO) {
val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri))
val stream = resource?.mContentStream ?: throw Exception("Error")
val data = stream.use { it.readBytes() }
session.cryptoService().importRoomKeys(data, password, null)
}
}
}

View File

@ -18,10 +18,13 @@ package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys
@ -30,7 +33,8 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast
import im.vector.app.features.crypto.keys.KeysExporter
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.launch
import javax.inject.Inject
class KeysBackupSetupActivity : SimpleFragmentActivity() {
@ -38,6 +42,13 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@Inject lateinit var keysExporter: KeysExporter
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
}
override fun initUiAndData() {
super.initUiAndData()
if (isFirstCreation()) {
@ -132,30 +143,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
showWaitingView()
KeysExporter(session)
.export(this@KeysBackupSetupActivity,
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
toast(getString(R.string.encryption_exported_successfully))
Intent().apply {
putExtra(MANUAL_EXPORT, true)
}.let {
setResult(Activity.RESULT_OK, it)
finish()
}
}
hideWaitingView()
}
override fun onFailure(failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
hideWaitingView()
}
})
export(passphrase, uri)
}
})
} else {
@ -165,6 +153,20 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
}
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
toast(getString(R.string.encryption_exported_successfully))
setResult(Activity.RESULT_OK, Intent().apply { putExtra(MANUAL_EXPORT, true) })
finish()
} catch (failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
}
hideWaitingView()
}
}
override fun onBackPressed() {
if (viewModel.shouldPromptOnBack) {
if (waitingView?.isVisible == true) {

View File

@ -20,6 +20,7 @@ package im.vector.app.features.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
@ -60,11 +61,11 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.launch
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
@ -75,6 +76,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore,
private val keysExporter: KeysExporter,
private val keysImporter: KeysImporter,
private val navigator: Navigator
) : VectorSettingsBaseFragment() {
@ -320,29 +323,24 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override fun onPassphrase(passphrase: String) {
displayLoadingView()
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
export(passphrase, uri)
}
})
}
}
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} catch (failure: Throwable) {
requireActivity().toast(errorFormatter.toHumanReadable(failure))
}
hideLoadingView()
}
}
private val pinActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
doOpenPinCodePreferenceScreen()
@ -474,34 +472,25 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
displayLoadingView()
KeysImporter(session)
.import(requireContext(),
uri,
mimetype,
password,
object : MatrixCallback<ImportRoomKeysResult> {
override fun onSuccess(data: ImportRoomKeysResult) {
if (!isAdded) {
return
}
hideLoadingView()
MaterialAlertDialogBuilder(thisActivity)
.setMessage(resources.getQuantityString(R.plurals.encryption_import_room_keys_success,
data.successfullyNumberOfImportedKeys,
data.successfullyNumberOfImportedKeys,
data.totalNumberOfKeys))
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
override fun onFailure(failure: Throwable) {
appContext.toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
hideLoadingView()
}
})
lifecycleScope.launch {
val data = try {
keysImporter.import(uri, mimetype, password)
} catch (failure: Throwable) {
appContext.toast(errorFormatter.toHumanReadable(failure))
null
}
hideLoadingView()
if (data != null) {
MaterialAlertDialogBuilder(thisActivity)
.setMessage(resources.getQuantityString(R.plurals.encryption_import_room_keys_success,
data.successfullyNumberOfImportedKeys,
data.successfullyNumberOfImportedKeys,
data.totalNumberOfKeys))
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
}
importDialog.dismiss()
}
}

View File

@ -42,9 +42,7 @@ import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import timber.log.Timber
import javax.inject.Inject
// TODO this needs to be refactored to current standard and remove legacy
@ -113,31 +111,6 @@ class SignOutBottomSheetDialogFragment :
views.setupMegolmBackupButton.action = {
setupBackupActivityResultLauncher.launch(KeysBackupSetupActivity.intent(requireContext(), true))
}
viewModel.observeViewEvents {
when (it) {
is SignoutCheckViewModel.ViewEvents.ExportKeys -> {
it.exporter
.export(requireContext(),
it.passphrase,
it.uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
viewModel.handle(SignoutCheckViewModel.Actions.KeySuccessfullyManuallyExported)
} else {
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
})
}
}
}
}
override fun invalidate() = withState(viewModel) { state ->

View File

@ -17,6 +17,7 @@
package im.vector.app.features.workers.signout
import android.net.Uri
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
@ -27,13 +28,14 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.crypto.keys.KeysExporter
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
@ -41,6 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_S
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
data class SignoutCheckViewState(
val userId: String = "",
@ -50,18 +53,15 @@ data class SignoutCheckViewState(
val hasBeenExportedToFile: Async<Boolean> = Uninitialized
) : MvRxState
class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState: SignoutCheckViewState,
private val session: Session)
: VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, SignoutCheckViewModel.ViewEvents>(initialState), KeysBackupStateListener {
class SignoutCheckViewModel @AssistedInject constructor(
@Assisted initialState: SignoutCheckViewState,
private val session: Session,
private val keysExporter: KeysExporter
) : VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, EmptyViewEvents>(initialState), KeysBackupStateListener {
sealed class Actions : VectorViewModelAction {
data class ExportKeys(val passphrase: String, val uri: Uri) : Actions()
object KeySuccessfullyManuallyExported : Actions()
object KeyExportFailed : Actions()
}
sealed class ViewEvents : VectorViewEvents {
data class ExportKeys(val exporter: KeysExporter, val passphrase: String, val uri: Uri) : ViewEvents()
}
@AssistedFactory
@ -128,22 +128,32 @@ class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: Actions) {
when (action) {
is Actions.ExportKeys -> {
setState {
copy(hasBeenExportedToFile = Loading())
}
_viewEvents.post(ViewEvents.ExportKeys(KeysExporter(session), action.passphrase, action.uri))
}
is Actions.ExportKeys -> handleExportKeys(action)
Actions.KeySuccessfullyManuallyExported -> {
setState {
copy(hasBeenExportedToFile = Success(true))
}
}
Actions.KeyExportFailed -> {
setState {
copy(hasBeenExportedToFile = Uninitialized)
}
}
}.exhaustive
}
private fun handleExportKeys(action: Actions.ExportKeys) {
setState {
copy(hasBeenExportedToFile = Loading())
}
viewModelScope.launch {
val newState = try {
keysExporter.export(action.passphrase, action.uri)
Success(true)
} catch (failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
Uninitialized
}
setState {
copy(hasBeenExportedToFile = newState)
}
}
}
}