use SingleLiveEvent for autocrypt transfer code
This commit is contained in:
parent
a6a8843d98
commit
9e25bbba04
7 changed files with 147 additions and 45 deletions
76
k9mail/src/main/java/com/fsck/k9/helper/SingleLiveEvent.java
Normal file
76
k9mail/src/main/java/com/fsck/k9/helper/SingleLiveEvent.java
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
}
|
|
@ -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()) }
|
||||
}
|
Loading…
Reference in a new issue