Merge pull request #264 from zcash/fix/refactor-startup
Fix/refactor startup
This commit is contained in:
commit
3a45ac5db2
|
@ -31,9 +31,6 @@
|
|||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Firebase options -->
|
||||
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" />
|
||||
|
||||
<!-- Mixpanel options -->
|
||||
<meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" />
|
||||
<meta-data android:name="com.mixpanel.android.MPConfig.EnableDebugLogging" android:value="false" />
|
||||
|
|
|
@ -81,11 +81,8 @@ import cash.z.ecc.android.ui.history.HistoryViewModel
|
|||
import cash.z.ecc.android.ui.util.MemoUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.RuntimeException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
@ -265,6 +262,7 @@ class MainActivity : AppCompatActivity() {
|
|||
(synchronizer as SdkSynchronizer).processor.onScanMetricCompleteListener = ::onScanMetricComplete
|
||||
|
||||
synchronizer.start(lifecycleScope)
|
||||
mainViewModel.setSyncReady(true)
|
||||
}
|
||||
} else {
|
||||
twig("Ignoring request to start sync because sync has already been started!")
|
||||
|
@ -311,24 +309,6 @@ class MainActivity : AppCompatActivity() {
|
|||
mainViewModel.setLoading(isLoading, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given block if the synchronizer is ready and syncing. Otherwise, wait until it is.
|
||||
* The block will be scoped to the synchronizer when it runs.
|
||||
*/
|
||||
fun launchWhenSyncing(block: suspend CoroutineScope.() -> Unit) {
|
||||
// TODO: update this quick and dirty implementation, after the holidays. For now, this gets
|
||||
// the job done but the synchronizer should expose a method that helps with this so that
|
||||
// any complexity is taken care of at the library level.
|
||||
lifecycleScope.launch {
|
||||
while (mainViewModel.isLoading) {
|
||||
delay(25L)
|
||||
}
|
||||
(synchronizerComponent.synchronizer() as SdkSynchronizer).coroutineScope.launch {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun authenticate(description: String, title: String = getString(R.string.biometric_prompt_title), block: () -> Unit) {
|
||||
val callback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
|
|
|
@ -4,13 +4,23 @@ import androidx.lifecycle.ViewModel
|
|||
import cash.z.ecc.android.sdk.ext.twig
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainViewModel @Inject constructor() : ViewModel() {
|
||||
private val _loadingMessage = MutableStateFlow<String?>("\u23F3 Loading...")
|
||||
private val _syncReady = MutableStateFlow(false)
|
||||
val loadingMessage: StateFlow<String?> get() = _loadingMessage
|
||||
val isLoading get() = loadingMessage.value != null
|
||||
|
||||
/**
|
||||
* A flow of booleans representing whether or not the synchronizer has been started. This is
|
||||
* useful for views that want to monitor the status of the wallet but don't want to access the
|
||||
* synchronizer before it is ready to be used. This is also helpful for race conditions where
|
||||
* the status of the synchronizer is needed before it is created.
|
||||
*/
|
||||
val syncReady = _syncReady.asStateFlow()
|
||||
|
||||
fun setLoading(isLoading: Boolean = false, message: String? = null) {
|
||||
twig("MainViewModel.setLoading: $isLoading")
|
||||
_loadingMessage.value = if (!isLoading) {
|
||||
|
@ -19,4 +29,9 @@ class MainViewModel @Inject constructor() : ViewModel() {
|
|||
message ?: "\u23F3 Loading..."
|
||||
}
|
||||
}
|
||||
|
||||
fun setSyncReady(isReady: Boolean) {
|
||||
twig("MainViewModel.setSyncReady: $isReady")
|
||||
_syncReady.value = isReady
|
||||
}
|
||||
}
|
|
@ -11,6 +11,9 @@ import androidx.viewbinding.ViewBinding
|
|||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
||||
val mainActivity: MainActivity? get() = activity as MainActivity?
|
||||
|
@ -58,4 +61,22 @@ abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
|||
mainActivity?.reportTap(tap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given block once, within the 'resumedScope', once the Synchronizer is ready. This
|
||||
* utility function helps solve the problem of taking action with the synchronizer before it
|
||||
* is created. This surfaced while loading keys from secure storage: the HomeFragment would
|
||||
* resume and start monitoring the synchronizer for changes BEFORE the onAttach function
|
||||
* returned, meaning before the synchronizerComponent is created. So a state variable needed to
|
||||
* exist with a longer lifecycle than the synchronizer. This function just takes care of all the
|
||||
* boilerplate of monitoring that state variable until it returns true.
|
||||
*/
|
||||
fun launchWhenSyncReady(block: () -> Unit) {
|
||||
resumedScope.launch {
|
||||
mainActivity?.let {
|
||||
it.mainViewModel.syncReady.filter { isReady -> isReady }.onEach {
|
||||
block()
|
||||
}.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,13 +76,11 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
arrowRotation = R.integer.transaction_arrow_rotation_send
|
||||
amountColor = R.color.transaction_sent
|
||||
if (toAddress.isShielded()) {
|
||||
arrowBackgroundTint = R.color.zcashYellow
|
||||
lineOneColor = R.color.zcashYellow
|
||||
} else {
|
||||
toAddress?.toAbbreviatedAddress()?.let {
|
||||
lineOne = lineOne.toColoredSpan(R.color.zcashBlueDark, it)
|
||||
}
|
||||
isLineOneSpanned = true
|
||||
}
|
||||
} else {
|
||||
arrowRotation = R.integer.transaction_arrow_rotation_pending
|
||||
|
@ -97,7 +95,6 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
if (senderAddress.isShielded()) {
|
||||
amountColor = R.color.zcashYellow
|
||||
lineOneColor = R.color.zcashYellow
|
||||
arrowBackgroundTint = R.color.zcashYellow
|
||||
} else {
|
||||
senderAddress.toAbbreviatedAddress().let {
|
||||
lineOne = if (senderAddress.equals(str(R.string.unknown), true)) {
|
||||
|
@ -106,7 +103,6 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
lineOne.toColoredSpan(R.color.zcashBlueDark, it)
|
||||
}
|
||||
}
|
||||
isLineOneSpanned = true
|
||||
}
|
||||
arrowRotation = R.integer.transaction_arrow_rotation_received
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.widget.TextView
|
|||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.DialogSolicitFeedbackRatingBinding
|
||||
import cash.z.ecc.android.databinding.FragmentHomeBinding
|
||||
|
@ -162,31 +163,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
maybeInterruptUser()
|
||||
mainActivity?.launchWhenSyncing {
|
||||
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
|
||||
val existingAmount = sendViewModel.zatoshiAmount.coerceAtLeast(0)
|
||||
viewModel.initializeMaybe(WalletZecFormmatter.toZecStringFull(existingAmount))
|
||||
if (existingAmount == 0L) onClearAmount()
|
||||
viewModel.uiModels.runningReduce { old, new ->
|
||||
onModelUpdated(old, new)
|
||||
new
|
||||
}.onCompletion {
|
||||
twig("uiModel.scanReduce completed.")
|
||||
}.catch { e ->
|
||||
twig("exception while processing uiModels $e")
|
||||
throw e
|
||||
}.launchIn(resumedScope)
|
||||
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
|
||||
|
||||
// TODO: see if there is a better way to trigger a refresh of the uiModel on resume
|
||||
// the latest one should just be in the viewmodel and we should just "resubscribe"
|
||||
// but for some reason, this doesn't always happen, which kind of defeats the purpose
|
||||
// of having a cold stream in the view model
|
||||
resumedScope.launch {
|
||||
viewModel.refreshBalance()
|
||||
}
|
||||
twig("HomeFragment.onResume COMPLETE")
|
||||
}
|
||||
launchWhenSyncReady(::onSyncReady)
|
||||
}
|
||||
|
||||
private fun onSyncReady() {
|
||||
twig("Sync ready! Monitoring synchronizer state...")
|
||||
monitorUiModelChanges()
|
||||
maybeInterruptUser()
|
||||
|
||||
twig("HomeFragment.onSyncReady COMPLETE")
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -411,6 +398,21 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
setBanner(getString(R.string.home_no_balance), FUND_NOW)
|
||||
}
|
||||
|
||||
private fun monitorUiModelChanges() {
|
||||
val existingAmount = sendViewModel.zatoshiAmount.coerceAtLeast(0)
|
||||
viewModel.initializeMaybe(WalletZecFormmatter.toZecStringFull(existingAmount))
|
||||
if (existingAmount == 0L) onClearAmount()
|
||||
viewModel.uiModels.runningReduce { old, new ->
|
||||
onModelUpdated(old, new)
|
||||
new
|
||||
}.onCompletion {
|
||||
twig("uiModel.scanReduce completed.")
|
||||
}.catch { e ->
|
||||
twig("exception while processing uiModels $e")
|
||||
throw e
|
||||
}.launchIn(resumedScope)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Inner classes and extensions
|
||||
|
|
|
@ -47,7 +47,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
mainActivity?.apply {
|
||||
sendViewModel.send().onEach {
|
||||
onPendingTxUpdated(it)
|
||||
}.launchIn(lifecycleScope)
|
||||
}.launchIn(resumedScope)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,14 +95,18 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
*/
|
||||
private suspend fun loadConfig(): Initializer.Config {
|
||||
twig("Loading config variables")
|
||||
var overwriteVks = false
|
||||
val network = ZcashWalletApp.instance.defaultNetwork
|
||||
val vk = loadUnifiedViewingKey() ?: onMissingViewingKey(network)
|
||||
val vk = loadUnifiedViewingKey() ?: onMissingViewingKey(network).also { overwriteVks = true }
|
||||
val birthdayHeight = loadBirthdayHeight() ?: onMissingBirthday(network)
|
||||
val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
|
||||
val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT
|
||||
|
||||
twig("Done loading config variables")
|
||||
return Initializer.Config { it.importWallet(vk, birthdayHeight, network, host, port) }
|
||||
return Initializer.Config {
|
||||
it.importWallet(vk, birthdayHeight, network, host, port)
|
||||
it.setOverwriteKeys(overwriteVks)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadUnifiedViewingKey(): UnifiedViewingKey? {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:text="1"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_2"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -46,7 +46,7 @@
|
|||
android:text="2"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintBottom_toBottomOf="@id/feedback_exp_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_3"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_1"
|
||||
app:layout_constraintTop_toTopOf="@id/feedback_exp_1" />
|
||||
|
@ -61,7 +61,7 @@
|
|||
android:text="3"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintBottom_toBottomOf="@id/feedback_exp_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_4"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_2"
|
||||
app:layout_constraintTop_toTopOf="@id/feedback_exp_1" />
|
||||
|
@ -76,7 +76,7 @@
|
|||
android:text="4"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintBottom_toBottomOf="@id/feedback_exp_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_5"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_3"
|
||||
app:layout_constraintTop_toTopOf="@id/feedback_exp_1" />
|
||||
|
|
|
@ -21,6 +21,7 @@ allprojects {
|
|||
repositories {
|
||||
// mavenLocal()
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ object Deps {
|
|||
const val buildToolsVersion = "29.0.2"
|
||||
const val minSdkVersion = 21
|
||||
const val targetSdkVersion = 29
|
||||
const val versionName = "1.0.0-alpha67"
|
||||
const val versionCode = 1_00_00_167 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
|
||||
const val versionName = "1.0.0-alpha69"
|
||||
const val versionCode = 1_00_00_169 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
|
||||
const val packageName = "cash.z.ecc.android"
|
||||
|
||||
|
||||
|
@ -83,7 +83,7 @@ object Deps {
|
|||
object Zcash {
|
||||
const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0"
|
||||
const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.1"
|
||||
const val SDK = "cash.z.ecc.android:zcash-android-sdk:1.3.0-beta05"
|
||||
const val SDK = "cash.z.ecc.android:zcash-android-sdk:1.3.0-beta08"
|
||||
}
|
||||
object Misc {
|
||||
const val LOTTIE = "com.airbnb.android:lottie:3.1.0"
|
||||
|
|
Loading…
Reference in New Issue