use SingleLiveEvent for autocrypt transfer code

This commit is contained in:
Vincent Breitmoser 2018-05-01 16:23:29 +02:00 committed by cketti
parent a6a8843d98
commit 9e25bbba04
7 changed files with 147 additions and 45 deletions

View file

@ -0,0 +1,76 @@
/*
* Copyright 2017 Google Inc.
*
* 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 com.fsck.k9.helper;
import java.util.concurrent.atomic.AtomicBoolean;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import timber.log.Timber;
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
* <p>
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
* <p>
* Note that only one observer is going to be notified of changes.
*/
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private final AtomicBoolean pending = new AtomicBoolean(false);
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<T> observer) {
if (hasActiveObservers()) {
Timber.w("Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (pending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
pending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void recall() {
setValue(getValue());
}
}

View file

@ -34,11 +34,11 @@ class AutocryptKeyTransferActivity : K9Activity(), AutocryptKeyTransferView {
val account = intent.getStringExtra(EXTRA_ACCOUNT)
presenter = AutocryptKeyTransferPresenter(applicationContext, this, this, viewModel, openPgpApiManager, transportProvider)
presenter.initFromIntent(account)
transfer_send_button.setOnClickListener { presenter.onClickTransferSend() }
transfer_show_code_button.setOnClickListener { presenter.onClickShowTransferCode() }
presenter = AutocryptKeyTransferPresenter(applicationContext, this, this, viewModel, openPgpApiManager, transportProvider)
presenter.initFromIntent(account)
}
override fun setAddress(address: String) {
@ -80,8 +80,10 @@ class AutocryptKeyTransferActivity : K9Activity(), AutocryptKeyTransferView {
transfer_show_code_button.visibility = View.GONE
}
override fun sceneFinished() {
setupSceneTransition()
override fun sceneFinished(transition: Boolean) {
if (transition) {
setupSceneTransition()
}
transfer_send_button.visibility = View.GONE
transfer_msg_info.visibility = View.GONE
@ -151,7 +153,7 @@ interface AutocryptKeyTransferView {
fun sceneBegin()
fun sceneGeneratingAndSending()
fun sceneSendError()
fun sceneFinished()
fun sceneFinished(transition: Boolean = true)
fun setLoadingStateGenerating()
fun setLoadingStateSending()
fun setLoadingStateSendingFailed()

View file

@ -5,16 +5,12 @@ import android.app.PendingIntent
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.Observer
import android.content.Context
import android.os.SystemClock
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.TransportProvider
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferLiveData.AutocryptSetupMessage
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import org.jetbrains.anko.coroutines.experimental.bg
import org.openintents.openpgp.OpenPgpApiManager
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError
@ -27,10 +23,11 @@ class AutocryptKeyTransferPresenter internal constructor(
private val transportProvider: TransportProvider) {
private lateinit var account: Account
private var showTransferCodePi: PendingIntent? = null
private lateinit var showTransferCodePi: PendingIntent
init {
viewModel.autocryptKeyTransferLiveData.observe(lifecycleOwner, Observer { msg -> onLoadedAutocryptSetupMessage(msg) })
viewModel.autocryptSetupMessageLiveEvent.observe(lifecycleOwner, Observer { msg -> onEventAutocryptSetupMessage(msg) })
viewModel.autocryptSetupTransferLiveEvent.observe(lifecycleOwner, Observer { pi -> onLoadedAutocryptSetupTransfer(pi) })
}
fun initFromIntent(accountUuid: String?) {
@ -54,7 +51,8 @@ class AutocryptKeyTransferPresenter internal constructor(
})
view.setAddress(account.identities[0].email)
view.sceneBegin()
viewModel.autocryptSetupTransferLiveEvent.recall()
}
fun onClickTransferSend() {
@ -64,47 +62,37 @@ class AutocryptKeyTransferPresenter internal constructor(
delay(1200) // ux delay, to give the scene transition some breathing room
view.setLoadingStateGenerating()
viewModel.autocryptKeyTransferLiveData.loadAutocryptSetupMessageAsync(
viewModel.autocryptSetupMessageLiveEvent.loadAutocryptSetupMessageAsync(
this@AutocryptKeyTransferPresenter.context.resources, openPgpApiManager.openPgpApi, account)
}
}
fun onClickShowTransferCode() {
if (showTransferCodePi == null) {
return
}
view.launchUserInteractionPendingIntent(showTransferCodePi!!)
view.launchUserInteractionPendingIntent(showTransferCodePi)
}
private fun onLoadedAutocryptSetupMessage(setupMsg: AutocryptSetupMessage?) {
private fun onEventAutocryptSetupMessage(setupMsg: AutocryptSetupMessage?) {
if (setupMsg == null) {
return
}
this.showTransferCodePi = setupMsg.showTransferCodePi
view.setLoadingStateSending()
view.sceneGeneratingAndSending()
val transport = transportProvider.getTransport(context, account)
launch(UI) {
val startTime = SystemClock.elapsedRealtime()
val msg = bg {
transport.sendMessage(setupMsg.setupMessage)
}
try {
msg.await()
val delayTime = 2000 - (SystemClock.elapsedRealtime() - startTime)
if (delayTime > 0) {
delay(delayTime)
}
viewModel.autocryptSetupTransferLiveEvent.sendMessageAsync(transport, setupMsg)
}
private fun onLoadedAutocryptSetupTransfer(result: AutocryptSetupTransferResult?) {
when (result) {
null -> view.sceneBegin()
is AutocryptSetupTransferResult.Success -> {
showTransferCodePi = result.showTransferCodePi
view.setLoadingStateFinished()
view.sceneFinished()
} catch (e: MessagingException) {
Timber.e(e, "Error sending setup message")
}
is AutocryptSetupTransferResult.Failure -> {
Timber.e(result.exception, "Error sending setup message")
view.setLoadingStateSendingFailed()
view.sceneSendError()
}

View file

@ -2,4 +2,6 @@ package com.fsck.k9.ui.endtoend
import android.arch.lifecycle.ViewModel
internal class AutocryptKeyTransferViewModel(val autocryptKeyTransferLiveData: AutocryptKeyTransferLiveData) : ViewModel()
internal class AutocryptKeyTransferViewModel(
val autocryptSetupMessageLiveEvent: AutocryptSetupMessageLiveEvent,
val autocryptSetupTransferLiveEvent: AutocryptSetupTransferLiveEvent) : ViewModel()

View file

@ -1,11 +1,11 @@
package com.fsck.k9.ui.endtoend
import android.app.PendingIntent
import android.arch.lifecycle.LiveData
import android.content.Intent
import android.content.res.Resources
import com.fsck.k9.Account
import com.fsck.k9.autocrypt.AutocryptTransferMessageUtil
import com.fsck.k9.helper.SingleLiveEvent
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message
import kotlinx.coroutines.experimental.android.UI
@ -15,7 +15,7 @@ import org.openintents.openpgp.util.OpenPgpApi
import java.io.ByteArrayOutputStream
import java.io.InputStream
class AutocryptKeyTransferLiveData : LiveData<AutocryptKeyTransferLiveData.AutocryptSetupMessage>() {
class AutocryptSetupMessageLiveEvent : SingleLiveEvent<AutocryptSetupMessage>() {
fun loadAutocryptSetupMessageAsync(resources: Resources, openPgpApi: OpenPgpApi, account: Account) {
launch(UI) {
val setupMessage = bg {
@ -40,8 +40,8 @@ class AutocryptKeyTransferLiveData : LiveData<AutocryptKeyTransferLiveData.Autoc
val setupMessage = AutocryptTransferMessageUtil.createAutocryptTransferMessage(resources, keyData, address)
return AutocryptSetupMessage(pi, setupMessage)
return AutocryptSetupMessage(setupMessage, pi)
}
data class AutocryptSetupMessage(val showTransferCodePi: PendingIntent, val setupMessage: Message)
}
data class AutocryptSetupMessage(val setupMessage: Message, val showTransferCodePi: PendingIntent)

View file

@ -0,0 +1,33 @@
package com.fsck.k9.ui.endtoend
import android.app.PendingIntent
import com.fsck.k9.helper.SingleLiveEvent
import com.fsck.k9.mail.Transport
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import org.jetbrains.anko.coroutines.experimental.bg
class AutocryptSetupTransferLiveEvent : SingleLiveEvent<AutocryptSetupTransferResult>() {
fun sendMessageAsync(transport: Transport, setupMsg: AutocryptSetupMessage) {
launch(UI) {
val setupMessage = bg {
transport.sendMessage(setupMsg.setupMessage)
}
delay(2000)
try {
setupMessage.await()
value = AutocryptSetupTransferResult.Success(setupMsg.showTransferCodePi)
} catch (e: Exception) {
value = AutocryptSetupTransferResult.Failure(e)
}
}
}
}
sealed class AutocryptSetupTransferResult {
data class Success(val showTransferCodePi: PendingIntent) : AutocryptSetupTransferResult()
data class Failure(val exception: Exception) : AutocryptSetupTransferResult()
}

View file

@ -4,6 +4,7 @@ import org.koin.android.architecture.ext.viewModel
import org.koin.dsl.module.applicationContext
val endToEndUiModule = applicationContext {
factory { AutocryptKeyTransferLiveData() }
viewModel { AutocryptKeyTransferViewModel(get()) }
factory { AutocryptSetupMessageLiveEvent() }
factory { AutocryptSetupTransferLiveEvent() }
viewModel { AutocryptKeyTransferViewModel(get(), get()) }
}