diff --git a/app/build.gradle b/app/build.gradle index 9aae3ab3b..ed58afe1a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,6 +95,16 @@ android { targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + } + buildFeatures { viewBinding true aidl true diff --git a/app/src/androidTest/java/com/tari/android/wallet/application/DeepLinkTest.kt b/app/src/androidTest/java/com/tari/android/wallet/application/DeepLinkTest.kt index 945fd389d..1199bd18f 100644 --- a/app/src/androidTest/java/com/tari/android/wallet/application/DeepLinkTest.kt +++ b/app/src/androidTest/java/com/tari/android/wallet/application/DeepLinkTest.kt @@ -50,14 +50,14 @@ class DeepLinkTest { fun assertBaseNodeName() { val deeplink = "tari://${currentNetwork.uriComponent}/${DeepLink.AddBaseNode.COMMAND_ADD_NODE}?${DeepLink.AddBaseNode.KEY_NAME}=base_node_test" - val result = deeplinkHandler.handle(deeplink) as? DeepLink.AddBaseNode + val result = deeplinkHandler.parseDeepLink(deeplink) as? DeepLink.AddBaseNode assertEquals(result!!.name, "base_node_test") } @Test fun assertBaseNodePeer() { val deeplink = "tari://${currentNetwork.uriComponent}/${DeepLink.AddBaseNode.COMMAND_ADD_NODE}?${DeepLink.AddBaseNode.KEY_PEER}=$PEER" - val result = deeplinkHandler.handle(deeplink) as? DeepLink.AddBaseNode + val result = deeplinkHandler.parseDeepLink(deeplink) as? DeepLink.AddBaseNode assertEquals(result!!.peer, PEER) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec47ca519..4c7d35541 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -192,7 +192,7 @@ (jDiscoveryTimeoutSec), static_cast(jSafDurationSec), + false, errorPointer ); jEnv->ReleaseStringUTFChars(jPublicAddress, pControlServiceAddress); diff --git a/app/src/main/cpp/jniSeedWords.cpp b/app/src/main/cpp/jniSeedWords.cpp index bb7ab8ed4..46540ccae 100644 --- a/app/src/main/cpp/jniSeedWords.cpp +++ b/app/src/main/cpp/jniSeedWords.cpp @@ -48,6 +48,24 @@ Java_com_tari_android_wallet_ffi_FFISeedWords_jniCreate( SetPointerField(jEnv, jThis, reinterpret_cast(pSeedWords)); } +extern "C" +JNIEXPORT void JNICALL +Java_com_tari_android_wallet_ffi_FFISeedWords_jniFromBase58( + JNIEnv *jEnv, + jobject jThis, + jstring jCypher, + jstring jPassphrase, + jobject error) { + ExecuteWithError(jEnv, error, [&](int *errorPointer) { + const char *pCypher = jEnv->GetStringUTFChars(jCypher, JNI_FALSE); + const char *pPassphrase = jEnv->GetStringUTFChars(jPassphrase, JNI_FALSE); + TariSeedWords *pSeedWords = seed_words_create_from_cipher(pCypher, pPassphrase, errorPointer); + jEnv->ReleaseStringUTFChars(jCypher, pCypher); + jEnv->ReleaseStringUTFChars(jPassphrase, pPassphrase); + SetPointerField(jEnv, jThis, reinterpret_cast(pSeedWords)); + }); +} + extern "C" JNIEXPORT void JNICALL Java_com_tari_android_wallet_ffi_FFISeedWords_jniGetMnemonicWordListForLanguage( @@ -72,7 +90,7 @@ Java_com_tari_android_wallet_ffi_FFISeedWords_jniPushWord( return ExecuteWithError(jEnv, error, [&](int *errorPointer) { auto pSeedWords = GetPointerField(jEnv, jThis); const char *pWord = jEnv->GetStringUTFChars(jWord, JNI_FALSE); - jint result = seed_words_push_word(pSeedWords, pWord, errorPointer); + jint result = seed_words_push_word(pSeedWords, pWord, nullptr, errorPointer); jEnv->ReleaseStringUTFChars(jWord, pWord); return result; }); diff --git a/app/src/main/cpp/jniWallet.cpp b/app/src/main/cpp/jniWallet.cpp index 1d34f0f48..95a2c8ee2 100644 --- a/app/src/main/cpp/jniWallet.cpp +++ b/app/src/main/cpp/jniWallet.cpp @@ -101,7 +101,7 @@ jmethodID balanceUpdatedCallbackMethodId; jmethodID walletScannedHeightCallbackMethodId; jmethodID baseNodeStatusCallbackMethodId; -void txBroadcastCallback(TariCompletedTransaction *pCompletedTransaction) { +void txBroadcastCallback(void *context, TariCompletedTransaction *pCompletedTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -111,7 +111,7 @@ void txBroadcastCallback(TariCompletedTransaction *pCompletedTransaction) { g_vm->DetachCurrentThread(); } -void txMinedCallback(TariCompletedTransaction *pCompletedTransaction) { +void txMinedCallback(void *context, TariCompletedTransaction *pCompletedTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -121,7 +121,7 @@ void txMinedCallback(TariCompletedTransaction *pCompletedTransaction) { g_vm->DetachCurrentThread(); } -void txMinedUnconfirmedCallback(TariCompletedTransaction *pCompletedTransaction, uint64_t confirmationCount) { +void txMinedUnconfirmedCallback(void *context, TariCompletedTransaction *pCompletedTransaction, uint64_t confirmationCount) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -132,7 +132,7 @@ void txMinedUnconfirmedCallback(TariCompletedTransaction *pCompletedTransaction, g_vm->DetachCurrentThread(); } -void txFauxConfirmedCallback(TariCompletedTransaction *pCompletedTransaction) { +void txFauxConfirmedCallback(void *context, TariCompletedTransaction *pCompletedTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -142,7 +142,7 @@ void txFauxConfirmedCallback(TariCompletedTransaction *pCompletedTransaction) { g_vm->DetachCurrentThread(); } -void txFauxUnconfirmedCallback(TariCompletedTransaction *pCompletedTransaction, uint64_t confirmationCount) { +void txFauxUnconfirmedCallback(void *context, TariCompletedTransaction *pCompletedTransaction, uint64_t confirmationCount) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -153,7 +153,7 @@ void txFauxUnconfirmedCallback(TariCompletedTransaction *pCompletedTransaction, g_vm->DetachCurrentThread(); } -void txReceivedCallback(TariPendingInboundTransaction *pPendingInboundTransaction) { +void txReceivedCallback(void *context, TariPendingInboundTransaction *pPendingInboundTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -163,7 +163,7 @@ void txReceivedCallback(TariPendingInboundTransaction *pPendingInboundTransactio g_vm->DetachCurrentThread(); } -void txReplyReceivedCallback(TariCompletedTransaction *pCompletedTransaction) { +void txReplyReceivedCallback(void *context, TariCompletedTransaction *pCompletedTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -173,7 +173,7 @@ void txReplyReceivedCallback(TariCompletedTransaction *pCompletedTransaction) { g_vm->DetachCurrentThread(); } -void txFinalizedCallback(TariCompletedTransaction *pCompletedTransaction) { +void txFinalizedCallback(void *context, TariCompletedTransaction *pCompletedTransaction) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -183,7 +183,7 @@ void txFinalizedCallback(TariCompletedTransaction *pCompletedTransaction) { g_vm->DetachCurrentThread(); } -void txDirectSendResultCallback(unsigned long long txId, TariTransactionSendStatus *status) { +void txDirectSendResultCallback(void *context, unsigned long long txId, TariTransactionSendStatus *status) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -194,7 +194,7 @@ void txDirectSendResultCallback(unsigned long long txId, TariTransactionSendStat } void -txCancellationCallback(TariCompletedTransaction *pCompletedTransaction, uint64_t rejectionReason) { +txCancellationCallback(void *context, TariCompletedTransaction *pCompletedTransaction, uint64_t rejectionReason) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -205,7 +205,7 @@ txCancellationCallback(TariCompletedTransaction *pCompletedTransaction, uint64_t g_vm->DetachCurrentThread(); } -void txoValidationCompleteCallback(uint64_t requestId, uint64_t status) { +void txoValidationCompleteCallback(void *context, uint64_t requestId, uint64_t status) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -216,7 +216,7 @@ void txoValidationCompleteCallback(uint64_t requestId, uint64_t status) { g_vm->DetachCurrentThread(); } -void contactsLivenessDataUpdatedCallback(TariContactsLivenessData *pTariContactsLivenessData) { +void contactsLivenessDataUpdatedCallback(void *context, TariContactsLivenessData *pTariContactsLivenessData) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -226,7 +226,7 @@ void contactsLivenessDataUpdatedCallback(TariContactsLivenessData *pTariContacts g_vm->DetachCurrentThread(); } -void transactionValidationCompleteCallback(uint64_t requestId, uint64_t status) { +void transactionValidationCompleteCallback(void *context, uint64_t requestId, uint64_t status) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -237,7 +237,7 @@ void transactionValidationCompleteCallback(uint64_t requestId, uint64_t status) g_vm->DetachCurrentThread(); } -void connectivityStatusCallback(uint64_t status) { +void connectivityStatusCallback(void *context, uint64_t status) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -247,7 +247,7 @@ void connectivityStatusCallback(uint64_t status) { g_vm->DetachCurrentThread(); } -void walletScannedHeightCallback(uint64_t height) { +void walletScannedHeightCallback(void *context, uint64_t height) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -257,7 +257,7 @@ void walletScannedHeightCallback(uint64_t height) { g_vm->DetachCurrentThread(); } -void balanceUpdatedCallback(TariBalance *pBalance) { +void balanceUpdatedCallback(void *context, TariBalance *pBalance) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -267,11 +267,11 @@ void balanceUpdatedCallback(TariBalance *pBalance) { g_vm->DetachCurrentThread(); } -void storeAndForwardMessagesReceivedCallback() { +void storeAndForwardMessagesReceivedCallback(void *context) { // no-op } -void baseNodeStatusCallback(TariBaseNodeState *pBaseNodeState) { +void baseNodeStatusCallback(void *context, TariBaseNodeState *pBaseNodeState) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -281,7 +281,7 @@ void baseNodeStatusCallback(TariBaseNodeState *pBaseNodeState) { g_vm->DetachCurrentThread(); } -void recoveringProcessCompleteCallback(uint8_t first, uint64_t second, uint64_t third) { +void recoveringProcessCompleteCallback(void *context, uint8_t first, uint64_t second, uint64_t third) { auto *jniEnv = getJNIEnv(); if (jniEnv == nullptr || callbackHandler == nullptr) { return; @@ -480,15 +480,18 @@ Java_com_tari_android_wallet_ffi_FFIWallet_jniCreate( } TariWallet *pWallet = wallet_create( + nullptr, pWalletConfig, pLogPath, logVerbosity, static_cast(maxNumberOfRollingLogFiles), static_cast(rollingLogFileMaxSizeBytes), pPassphrase, + nullptr, pSeedWords, pNetwork, pDnsPeer, + nullptr, isDnsSecureOn, txReceivedCallback, txReplyReceivedCallback, @@ -580,7 +583,7 @@ Java_com_tari_android_wallet_ffi_FFIWallet_jniGetWalletAddress( jobject error) { return ExecuteWithErrorAndCast(jEnv, error, [&](int *errorPointer) { auto pWallet = GetPointerField(jEnv, jThis); - return wallet_get_tari_address(pWallet, errorPointer); + return wallet_get_tari_interactive_address(pWallet, errorPointer); }); } diff --git a/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodesManager.kt b/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodesManager.kt index 5b908bd11..3ca0b6102 100644 --- a/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodesManager.kt +++ b/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodesManager.kt @@ -1,29 +1,19 @@ package com.tari.android.wallet.application.baseNodes import android.content.Context -import com.google.gson.Gson import com.orhanobut.logger.Logger import com.tari.android.wallet.R import com.tari.android.wallet.application.Network -import com.tari.android.wallet.application.walletManager.WalletStateHandler import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeList import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodePrefRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository -import com.tari.android.wallet.di.ApplicationScope -import com.tari.android.wallet.extension.getWithError -import com.tari.android.wallet.ffi.FFIPublicKey import com.tari.android.wallet.ffi.FFITariBaseNodeState import com.tari.android.wallet.ffi.FFIWallet -import com.tari.android.wallet.ffi.HexString -import com.tari.android.wallet.service.connection.TariWalletServiceConnection import com.tari.android.wallet.util.DebugConfig -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch import org.apache.commons.io.IOUtils import java.math.BigInteger import javax.inject.Inject @@ -39,9 +29,6 @@ class BaseNodesManager @Inject constructor( private val context: Context, private val baseNodeSharedRepository: BaseNodePrefRepository, private val networkRepository: NetworkPrefRepository, - private val serviceConnection: TariWalletServiceConnection, - private val walletStateHandler: WalletStateHandler, - @ApplicationScope private val applicationScope: CoroutineScope, ) { private val logger get() = Logger.t(this::class.simpleName) @@ -63,6 +50,12 @@ class BaseNodesManager @Inject constructor( private val _networkBlockHeight = MutableStateFlow(BigInteger.ZERO) val networkBlockHeight = _networkBlockHeight.asStateFlow() + val currentBaseNode: BaseNodeDto? + get() = baseNodeSharedRepository.currentBaseNode + + val userBaseNodes: List + get() = baseNodeSharedRepository.userBaseNodes + /** * Select a base node randomly from the list of base nodes in base_nodes.tx, and sets * the wallet and stored the values in shared prefs. @@ -73,16 +66,17 @@ class BaseNodesManager @Inject constructor( val currentBaseNode = baseNodeSharedRepository.currentBaseNode ?: baseNodeList.firstOrNull() val nextBaseNode = baseNodeList.getOrNull(baseNodeList.indexOf(currentBaseNode) + 1) - baseNodeSharedRepository.baseNodeLastSyncResult = null baseNodeSharedRepository.currentBaseNode = nextBaseNode return nextBaseNode } + /** + * Sets the base node to the given base node. + * Need to call WalletManager.syncBaseNode() after this method. + */ fun setBaseNode(baseNode: BaseNodeDto) { - baseNodeSharedRepository.baseNodeLastSyncResult = null baseNodeSharedRepository.currentBaseNode = baseNode - startSync() } fun addUserBaseNode(baseNode: BaseNodeDto) { @@ -107,40 +101,6 @@ class BaseNodesManager @Inject constructor( _walletScannedHeight.update { height } } - fun startSync() { - //essential for wallet creation flow - var currentBaseNode: BaseNodeDto? = baseNodeSharedRepository.currentBaseNode ?: return - - applicationScope.launch(Dispatchers.IO) { - serviceConnection.doOnWalletServiceConnected { walletService -> - walletStateHandler.doOnWalletRunning { ffiWallet -> - while (currentBaseNode != null) { - try { - currentBaseNode?.let { - logger.i("startSync") - logger.i("startSync:publicKeyHex: ${it.publicKeyHex}") - logger.i("startSync:address: ${it.address}") - logger.i("startSync:userBaseNodes: ${Gson().toJson(baseNodeSharedRepository.userBaseNodes)}") - val baseNodeKeyFFI = FFIPublicKey(HexString(it.publicKeyHex)) - ffiWallet.addBaseNodePeer(baseNodeKeyFFI, it.address) - baseNodeKeyFFI.destroy() - walletService.getWithError { error, wallet -> wallet.startBaseNodeSync(error) } - } - break - } catch (e: Throwable) { - logger.i("startSync:error connecting to base node $currentBaseNode with an error: ${e.message}") - currentBaseNode = setNextBaseNode() - } - } - - if (currentBaseNode == null) { - logger.e("startSync: cannot connect to any base node") - } - } - } - } - } - /** * address should be in the format of hex::/onion3/{public_key} or hex::/ip4/{ip}/tcp/{port} */ @@ -148,17 +108,17 @@ class BaseNodesManager @Inject constructor( return Regex(REGEX_ONION).matches(address) || Regex(REGEX_IPV4).matches(address) } - fun refreshBaseNodeList() { - baseNodeSharedRepository.ffiBaseNodes = loadBaseNodesFromFFI() + fun refreshBaseNodeList(wallet: FFIWallet) { + baseNodeSharedRepository.ffiBaseNodes = loadBaseNodesFromFFI(wallet) } - private fun loadBaseNodesFromFFI(): BaseNodeList = FFIWallet.instance?.getBaseNodePeers() - ?.mapIndexed { index, publicKey -> + fun loadBaseNodesFromFFI(wallet: FFIWallet): BaseNodeList = wallet.getBaseNodePeers() + .mapIndexed { index, publicKey -> BaseNodeDto( name = "${networkRepository.currentNetwork.network.displayName} ${index + 1}", publicKeyHex = publicKey.hex, ) - }.orEmpty() + } .let { BaseNodeList(it) } .also { list -> logger.i("baseNodeList from FFI: \n${list.joinToString(separator = "\n")}") } diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt index 6c035c0e0..6a8ac1a1e 100644 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt @@ -32,23 +32,28 @@ */ package com.tari.android.wallet.application.deeplinks +import android.os.Parcelable import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfiguration import com.tari.android.wallet.ffi.Base58 +import com.tari.android.wallet.ffi.FFISeedWords +import com.tari.android.wallet.ffi.runWithDestroy import com.tari.android.wallet.model.MicroTari import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.util.parseToBigInteger +import kotlinx.parcelize.Parcelize /** * Parses a deep link and contains the structured deep link details. * * @author The Tari Development Team */ -sealed class DeepLink { +sealed class DeepLink : Parcelable { open fun getParams(): Map = emptyMap() open fun getCommand(): String = "" // tari://esmeralda/contacts?list[0][alias]=Name&list[0][tariAddress]=tariAddress&list[1][alias]=Name&list[1][tariAddress]=tariAddress + @Parcelize data class Contacts(val contacts: List) : DeepLink() { constructor(params: Map) : this( @@ -79,7 +84,8 @@ sealed class DeepLink { const val KEY_TARI_ADDRESS = "tariAddress" } - data class DeeplinkContact(val alias: String, val tariAddress: Base58) + @Parcelize + data class DeeplinkContact(val alias: String, val tariAddress: Base58) : Parcelable class FormatExtractor(val key: String, val value: String = "") { val index: Int @@ -97,6 +103,7 @@ sealed class DeepLink { } } + @Parcelize data class Send(val walletAddress: Base58 = "", val amount: MicroTari? = null, val note: String = "") : DeepLink() { constructor(params: Map) : this( @@ -121,6 +128,7 @@ sealed class DeepLink { } } + @Parcelize data class UserProfile(val tariAddress: Base58 = "", val alias: String = "") : DeepLink() { constructor(params: Map) : this( @@ -142,6 +150,7 @@ sealed class DeepLink { } } + @Parcelize data class AddBaseNode(val name: String = "", val peer: String = "") : DeepLink() { constructor(params: Map) : this( @@ -163,8 +172,33 @@ sealed class DeepLink { } } + @Parcelize data class TorBridges(val torConfigurations: List) : DeepLink() + // tari://esmeralda/paper_wallet?private_key=1LTWgW1kzx1e1EoY9vU1FCSDweVKjnJuNA9LysUJnFuy3x + @Parcelize + data class PaperWallet(val privateKey: String) : DeepLink() { + + fun seedWords(passphrase: String): List? = runCatching { + FFISeedWords(this.privateKey, passphrase).runWithDestroy { seedWords -> (0 until seedWords.getLength()).map { seedWords.getAt(it) } } + }.getOrNull() + + constructor(params: Map) : this( + params[KEY_PRIVATE_KEY].orEmpty() + ) + + override fun getParams(): Map = hashMapOf().apply { + put(KEY_PRIVATE_KEY, privateKey) + } + + override fun getCommand(): String = COMMAND_PAPER_WALLET + + companion object { + const val COMMAND_PAPER_WALLET = "paper_wallet" + const val KEY_PRIVATE_KEY = "private_key" + } + } + companion object { fun getByCommand(command: String, params: Map): DeepLink? = when (command) { @@ -172,6 +206,7 @@ sealed class DeepLink { Send.COMMAND_SEND -> Send(params) AddBaseNode.COMMAND_ADD_NODE -> AddBaseNode(params) UserProfile.COMMAND_PROFILE -> UserProfile(params) + PaperWallet.COMMAND_PAPER_WALLET -> PaperWallet(params) else -> null } } diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkHandler.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkHandler.kt deleted file mode 100644 index 52cdc1f0f..000000000 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkHandler.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tari.android.wallet.application.deeplinks - -import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository -import javax.inject.Inject -import javax.inject.Singleton - - -@Singleton -class DeeplinkHandler @Inject constructor(networkRepository: NetworkPrefRepository) { - - private val deeplinkFormatter = DeeplinkFormatter(networkRepository) - - fun handle(deepLink: String): DeepLink? = deeplinkFormatter.parse(deepLink) - - fun getDeeplink(deeplink: DeepLink): String = deeplinkFormatter.toDeeplink(deeplink) -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkManager.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkManager.kt new file mode 100644 index 000000000..93bdf6184 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkManager.kt @@ -0,0 +1,288 @@ +package com.tari.android.wallet.application.deeplinks + +import android.app.Activity +import com.tari.android.wallet.R +import com.tari.android.wallet.application.baseNodes.BaseNodesManager +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto +import com.tari.android.wallet.data.sharedPrefs.tor.TorPrefRepository +import com.tari.android.wallet.di.ApplicationScope +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.ui.common.DialogManager +import com.tari.android.wallet.ui.common.domain.ResourceManager +import com.tari.android.wallet.ui.dialog.confirm.ConfirmDialogArgs +import com.tari.android.wallet.ui.dialog.modular.InputModularDialog +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs.DialogId +import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule +import com.tari.android.wallet.ui.dialog.modular.modules.input.InputModule +import com.tari.android.wallet.ui.fragment.contactBook.data.ContactsRepository +import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.ContactDto +import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.FFIContactInfo +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator +import com.tari.android.wallet.util.DebugConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DeeplinkManager @Inject constructor( + private val baseNodesManager: BaseNodesManager, + private val contactRepository: ContactsRepository, + private val torSharedRepository: TorPrefRepository, + private val resourceManager: ResourceManager, + private val dialogManager: DialogManager, + private val walletManager: WalletManager, + private val navigator: TariNavigator, + private val deeplinkParser: DeeplinkParser, + @ApplicationScope private val applicationScope: CoroutineScope, +) { + + fun parseDeepLink(deepLink: String): DeepLink? = deeplinkParser.parse(deepLink) + + fun getDeeplinkString(deeplink: DeepLink): String = deeplinkParser.toDeeplink(deeplink) + + /** + * Executes the given deeplink, but first shows a confirmation dialog. + */ + fun execute(context: Activity, deeplink: DeepLink) { + when (deeplink) { + is DeepLink.AddBaseNode -> showAddBaseNodeDialog(context, deeplink) + is DeepLink.Contacts -> showAddContactsDialog(context, deeplink) + is DeepLink.Send -> sendAction(deeplink) + is DeepLink.UserProfile -> addUserProfile(context, deeplink) + is DeepLink.TorBridges -> addTorBridges(deeplink) + is DeepLink.PaperWallet -> showPaperWalletDialog(context, deeplink) + } + } + + /** + * Executes the given deeplink without showing a confirmation dialog. + */ + fun executeRawDeeplink(context: Activity, deeplink: DeepLink) { + when (deeplink) { + is DeepLink.AddBaseNode -> showAddBaseNodeDialog(context, deeplink) + is DeepLink.Contacts -> addContactsAction(deeplink.data()) + is DeepLink.Send -> sendAction(deeplink) + is DeepLink.UserProfile -> addContactsAction(deeplink.data()?.let { listOf(it) } ?: emptyList()) + is DeepLink.TorBridges -> addTorBridges(deeplink) + is DeepLink.PaperWallet -> showPaperWalletDialog(context, deeplink) + } + } + + private fun showAddBaseNodeDialog(context: Activity, deeplink: DeepLink.AddBaseNode) { + val baseNode = deeplink.data() + dialogManager.replace( + context = context, + args = ConfirmDialogArgs( + dialogId = DialogId.DEEPLINK_ADD_BASE_NODE, + title = resourceManager.getString(R.string.home_custom_base_node_title), + description = resourceManager.getString(R.string.home_custom_base_node_description), + cancelButtonText = resourceManager.getString(R.string.home_custom_base_node_no_button), + confirmButtonText = resourceManager.getString(R.string.common_lets_do_it), + onConfirm = { + dialogManager.dismiss(DialogId.DEEPLINK_ADD_BASE_NODE) + addBaseNodeAction(context, baseNode) + }, + ).getModular(baseNode, resourceManager), + ) + } + + private fun addUserProfile(context: Activity, deeplink: DeepLink.UserProfile) { + val contact = DeepLink.Contacts( + listOf( + DeepLink.Contacts.DeeplinkContact( + alias = deeplink.alias, + tariAddress = deeplink.tariAddress, + ) + ) + ) + showAddContactsDialog(context, contact) + } + + private fun showAddContactsDialog(context: Activity, deeplink: DeepLink.Contacts) { + val contactDtos = deeplink.data() + if (contactDtos.isEmpty()) return + val names = contactDtos.joinToString(", ") { it.contactInfo.getAlias().trim() } + dialogManager.replace( + context = context, + args = ModularDialogArgs( + dialogId = DialogId.DEEPLINK_ADD_CONTACTS, + modules = listOf( + HeadModule(resourceManager.getString(R.string.contact_deeplink_title)), + BodyModule(resourceManager.getString(R.string.contact_deeplink_message, contactDtos.size.toString()) + ". " + names), + ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Normal) { + addContactsAction(contactDtos) + dialogManager.dismiss(DialogId.DEEPLINK_ADD_CONTACTS) + }, + ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) + ), + ) + ) + } + + private fun addTorBridges(deeplink: DeepLink.TorBridges) { + deeplink.torConfigurations.forEach { + torSharedRepository.addTorBridgeConfiguration(it) + } + } + + private fun showPaperWalletDialog(context: Activity, deeplink: DeepLink.PaperWallet) { + dialogManager.replace( + context = context, + args = ModularDialogArgs( + dialogId = DialogId.DEEPLINK_PAPER_WALLET, + modules = listOfNotNull( + HeadModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_title)), + BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_body)), + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_sweep_funds_button), ButtonStyle.Normal) { + dialogManager.dismiss(DialogId.DEEPLINK_PAPER_WALLET) + dialogManager.showNotReadyYetDialog(context) + }.takeIf { DebugConfig.sweepFundsButtonEnabled }, + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_replace_wallet_button), ButtonStyle.Normal) { + dialogManager.dismiss(DialogId.DEEPLINK_PAPER_WALLET) + showRememberToBackupDialog(context, deeplink) + }, + ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close), + ), + ) + ) + } + + private fun showRememberToBackupDialog(context: Activity, deeplink: DeepLink.PaperWallet) { + dialogManager.replace( + context = context, + args = ModularDialogArgs( + dialogId = DialogId.DEEPLINK_PAPER_WALLET_REMEMBER_TO_BACKUP, + modules = listOf( + HeadModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_remember_backup_title)), + BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_remember_backup_body)), + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_remember_backup_yes_button), ButtonStyle.Normal) { + dialogManager.dismiss(DialogId.DEEPLINK_PAPER_WALLET_REMEMBER_TO_BACKUP) + goToBackupAction() + }, + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_remember_backup_no_button), ButtonStyle.Normal) { + dialogManager.dismiss(DialogId.DEEPLINK_PAPER_WALLET_REMEMBER_TO_BACKUP) + showEnterPassphraseDialog(context, deeplink) + }, + ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close), + ), + ) + ) + } + + private fun showEnterPassphraseDialog(context: Activity, deeplink: DeepLink.PaperWallet) { + var saveAction: () -> Boolean = { false } + + val headModule = HeadModule( + title = resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_title), + rightButtonTitle = resourceManager.getString(R.string.common_done), + rightButtonAction = { saveAction() }, + ) + + val bodyModule = BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_body)) + + val passphraseModule = InputModule( + value = "", + hint = resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_hint), + isFirst = true, + isEnd = true, + onDoneAction = { saveAction() }, + ) + + saveAction = { + val seeds = deeplink.seedWords(passphraseModule.value.trim()) + if (seeds != null) { + dialogManager.dismiss(DialogId.DEEPLINK_PAPER_WALLET_ENTER_PASSPHRASE) + replaceWalletAction(seeds) + } else { + showPaperWalletErrorDialog(context) + } + true + } + + dialogManager.replace( + InputModularDialog( + context = context, + args = ModularDialogArgs( + dialogId = DialogId.DEEPLINK_PAPER_WALLET_ENTER_PASSPHRASE, + modules = listOf( + headModule, + bodyModule, + passphraseModule, + ), + ) + ) + ) + } + + private fun showPaperWalletErrorDialog(context: Activity) { + dialogManager.replace( + context = context, + args = ModularDialogArgs( + modules = listOf( + HeadModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_error_title)), + BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_error_body)), + ButtonModule(resourceManager.getString(R.string.common_close), ButtonStyle.Close), + ), + ) + ) + } + + private fun DeepLink.AddBaseNode.data(): BaseNodeDto = BaseNodeDto.fromDeeplink(this) + + private fun DeepLink.Contacts.data(): List = this.contacts.mapNotNull { + runCatching { + val tariWalletAddress = TariWalletAddress.fromBase58(it.tariAddress) + ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = it.alias)) + }.getOrNull() + } + + private fun DeepLink.Send.data(): ContactDto? = runCatching { + val tariWalletAddress = TariWalletAddress.fromBase58(this.walletAddress) + ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = "")) + }.getOrNull() + + private fun DeepLink.UserProfile.data(): ContactDto? = runCatching { + val tariWalletAddress = TariWalletAddress.fromBase58(this.tariAddress) + ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = this.alias)) + }.getOrNull() + + private fun addContactsAction(contacts: List) { + applicationScope.launch(Dispatchers.IO) { + contactRepository.addContactList(contacts) + } + } + + private fun sendAction(deeplink: DeepLink.Send) { + navigator.navigate(Navigation.TxListNavigation.ToSendWithDeeplink(deeplink)) + } + + private fun addBaseNodeAction(context: Activity, baseNodeDto: BaseNodeDto) { + if (DebugConfig.selectBaseNodeEnabled) { + baseNodesManager.addUserBaseNode(baseNodeDto) + baseNodesManager.setBaseNode(baseNodeDto) + walletManager.syncBaseNode() + } else { + dialogManager.showNotReadyYetDialog(context) + } + } + + private fun goToBackupAction() { + navigator.let { + it.toAllSettings() + it.toBackupSettings(true) + } + } + + private fun replaceWalletAction(seedWords: List) { + walletManager.deleteWallet() + navigator.navigate(Navigation.SplashScreen(seedWords)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkParser.kt similarity index 78% rename from app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt rename to app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkParser.kt index eb7450006..9d423ff49 100644 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkParser.kt @@ -8,8 +8,10 @@ import java.net.URLDecoder import javax.inject.Inject import javax.inject.Singleton + @Singleton -class DeeplinkFormatter @Inject constructor(private val networkRepository: NetworkPrefRepository) { +class DeeplinkParser @Inject constructor(private val networkRepository: NetworkPrefRepository) { + fun parse(deepLink: String): DeepLink? { val torBridges = getTorDeeplink(deepLink) if (torBridges.isNotEmpty()) { @@ -22,17 +24,17 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo return null } - var paramentrs = uri.queryParameterNames.associateWith { uri.getQueryParameter(it).orEmpty() }.toMutableMap() val command = uri.path.orEmpty().trimStart('/') - if (command == DeepLink.Contacts.COMMAND_CONTACTS) { - val values = uri.query.orEmpty().split("&").map { + val parameters = if (command == DeepLink.Contacts.COMMAND_CONTACTS) { // list params + uri.query.orEmpty().split("&").associate { val (key, value) = it.split("=") key to value - }.toMap() - paramentrs = values.toMutableMap() + } + } else { + uri.queryParameterNames.associateWith { uri.getQueryParameter(it).orEmpty() } } - return DeepLink.getByCommand(command, paramentrs)?.takeIf { + return DeepLink.getByCommand(command, parameters)?.takeIf { when (it) { is DeepLink.Send -> TariWalletAddress.validateBase58(it.walletAddress) is DeepLink.UserProfile -> TariWalletAddress.validateBase58(it.tariAddress) @@ -49,7 +51,7 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo } val fullPart = Uri.Builder() - .scheme(scheme) + .scheme(SCHEME) .authority(networkRepository.currentNetwork.network.uriComponent) .appendPath(deepLink.getCommand()) @@ -61,7 +63,7 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo } private fun getTorDeeplink(input: String): List { - return regex.findAll(input).mapNotNull { match -> + return REGEX.findAll(input).mapNotNull { match -> try { val ipAddressAndPort = match.groupValues[1].split(":") val sha1Hash = match.groupValues[2] @@ -73,8 +75,7 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo } companion object { - const val scheme = "tari" - - val regex = Regex("""(\d+\.\d+\.\d+\.\d+:\d+) ([0-9A-Fa-f]+)""") + const val SCHEME = "tari" + val REGEX = Regex("""(\d+\.\d+\.\d+\.\d+:\d+) ([0-9A-Fa-f]+)""") } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt deleted file mode 100644 index f0704132f..000000000 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.tari.android.wallet.application.deeplinks - -import androidx.lifecycle.viewModelScope -import com.tari.android.wallet.R -import com.tari.android.wallet.application.baseNodes.BaseNodesManager -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto -import com.tari.android.wallet.data.sharedPrefs.tor.TorPrefRepository -import com.tari.android.wallet.model.TariWalletAddress -import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.dialog.confirm.ConfirmDialogArgs -import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule -import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule -import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle -import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule -import com.tari.android.wallet.ui.fragment.contactBook.data.ContactsRepository -import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.ContactDto -import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.FFIContactInfo -import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import javax.inject.Inject - -class DeeplinkViewModel : CommonViewModel() { - - @Inject - lateinit var baseNodesManager: BaseNodesManager - - @Inject - lateinit var contactRepository: ContactsRepository - - @Inject - lateinit var deeplinkHandler: DeeplinkHandler - - @Inject - lateinit var torSharedRepository: TorPrefRepository - - init { - component.inject(this) - } - - fun tryToHandle(qrData: String, isQrData: Boolean = true) { - deeplinkHandler.handle(qrData)?.let { execute(it, isQrData) } - } - - fun execute(deeplink: DeepLink, isQrData: Boolean = true) { - when (deeplink) { - is DeepLink.AddBaseNode -> addBaseNode(deeplink, isQrData) - is DeepLink.Contacts -> addContacts(deeplink, isQrData) - is DeepLink.Send -> sendAction(deeplink, isQrData) - is DeepLink.UserProfile -> addUserProfile(deeplink, isQrData) - is DeepLink.TorBridges -> addTorBridges(deeplink, isQrData) - } - } - - private fun addBaseNode(deeplink: DeepLink.AddBaseNode, isQrData: Boolean = true) { - val baseNode = getData(deeplink) - val args = ConfirmDialogArgs( - title = resourceManager.getString(R.string.home_custom_base_node_title), - description = resourceManager.getString(R.string.home_custom_base_node_description), - cancelButtonText = resourceManager.getString(R.string.home_custom_base_node_no_button), - confirmButtonText = resourceManager.getString(R.string.common_lets_do_it), - onConfirm = { - hideDialog() - addBaseNodeAction(baseNode, isQrData) - }, - ).getModular(baseNode, resourceManager) - showModularDialog(args) - } - - private fun addUserProfile(deeplink: DeepLink.UserProfile, isQrData: Boolean) { - val contact = DeepLink.Contacts( - listOf( - DeepLink.Contacts.DeeplinkContact( - alias = deeplink.alias, - tariAddress = deeplink.tariAddress, - ) - ) - ) - addContacts(contact, isQrData) - } - - fun addContacts(contacts: DeepLink.Contacts, isQrData: Boolean = true) { - val contactDtos = getData(contacts) - if (contactDtos.isEmpty()) return - val names = contactDtos.joinToString(", ") { it.contactInfo.getAlias().trim() } - showModularDialog( - HeadModule(resourceManager.getString(R.string.contact_deeplink_title)), - BodyModule(resourceManager.getString(R.string.contact_deeplink_message, contactDtos.size.toString()) + ". " + names), - ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Normal) { - addContactsAction(contactDtos, isQrData) - hideDialog() - }, - ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) - ) - } - - fun addTorBridges(deeplink: DeepLink.TorBridges, isQrData: Boolean) { - deeplink.torConfigurations.forEach { - torSharedRepository.addTorBridgeConfiguration(it) - } - } - - fun executeRawDeeplink(deeplink: DeepLink, isQrData: Boolean = true) { - when (deeplink) { - is DeepLink.AddBaseNode -> addBaseNode(deeplink) - is DeepLink.Contacts -> addContactsAction(getData(deeplink), isQrData) - is DeepLink.Send -> sendAction(deeplink, isQrData) - is DeepLink.UserProfile -> addContactsAction(getData(deeplink)?.let { listOf(it) } ?: listOf(), isQrData) - is DeepLink.TorBridges -> addTorBridges(deeplink, isQrData) - } - } - - private fun getData(deeplink: DeepLink.AddBaseNode): BaseNodeDto = BaseNodeDto.fromDeeplink(deeplink) - - private fun getData(deeplink: DeepLink.Contacts): List = deeplink.contacts.mapNotNull { - runCatching { - val tariWalletAddress = TariWalletAddress.fromBase58(it.tariAddress) - ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = it.alias)) - }.getOrNull() - } - - private fun getData(deeplink: DeepLink.Send): ContactDto? = runCatching { - val tariWalletAddress = TariWalletAddress.fromBase58(deeplink.walletAddress) - ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = "")) - }.getOrNull() - - private fun getData(userProfile: DeepLink.UserProfile): ContactDto? = runCatching { - val tariWalletAddress = TariWalletAddress.fromBase58(userProfile.tariAddress) - ContactDto(FFIContactInfo(walletAddress = tariWalletAddress, alias = userProfile.alias)) - }.getOrNull() - - private fun addContactsAction(contacts: List, isQrData: Boolean) { - viewModelScope.launch(Dispatchers.IO) { - contactRepository.addContactList(contacts) - } - } - - private fun sendAction(deeplink: DeepLink.Send, isQrData: Boolean) { - navigation.postValue(Navigation.TxListNavigation.ToSendWithDeeplink(deeplink)) - } - - private fun addBaseNodeAction(baseNodeDto: BaseNodeDto, isQrData: Boolean) { - baseNodesManager.addUserBaseNode(baseNodeDto) - baseNodesManager.setBaseNode(baseNodeDto) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/util/WalletUtil.kt b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletFileUtil.kt similarity index 96% rename from app/src/main/java/com/tari/android/wallet/util/WalletUtil.kt rename to app/src/main/java/com/tari/android/wallet/application/walletManager/WalletFileUtil.kt index 5c94f9263..6b4e1c6f8 100644 --- a/app/src/main/java/com/tari/android/wallet/util/WalletUtil.kt +++ b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletFileUtil.kt @@ -30,7 +30,7 @@ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.tari.android.wallet.util +package com.tari.android.wallet.application.walletManager import com.tari.android.wallet.data.WalletConfig import java.io.File @@ -38,12 +38,7 @@ import java.math.RoundingMode import java.text.DecimalFormat import java.util.* -/** - * Wallet utility functions. - * - * @author The Tari Development Team - */ -object WalletUtil { +object WalletFileUtil { val balanceFormatter = DecimalFormat("#,##0.00").apply { roundingMode = RoundingMode.FLOOR diff --git a/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletManager.kt b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletManager.kt index 52c7f5cf9..4893d9c71 100644 --- a/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletManager.kt +++ b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletManager.kt @@ -32,36 +32,80 @@ */ package com.tari.android.wallet.application.walletManager +import com.google.gson.Gson import com.orhanobut.logger.Logger import com.tari.android.wallet.BuildConfig import com.tari.android.wallet.application.Network +import com.tari.android.wallet.application.TariWalletApplication import com.tari.android.wallet.application.baseNodes.BaseNodesManager import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodePrefRepository +import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsPrefRepository import com.tari.android.wallet.di.ApplicationScope +import com.tari.android.wallet.event.EffectChannelFlow +import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.safeCastTo import com.tari.android.wallet.ffi.FFIByteVector import com.tari.android.wallet.ffi.FFICommsConfig import com.tari.android.wallet.ffi.FFIException +import com.tari.android.wallet.ffi.FFIPublicKey +import com.tari.android.wallet.ffi.FFISeedWords +import com.tari.android.wallet.ffi.FFITariBaseNodeState import com.tari.android.wallet.ffi.FFITariTransportConfig import com.tari.android.wallet.ffi.FFIWallet +import com.tari.android.wallet.ffi.FFIWalletListener +import com.tari.android.wallet.ffi.HexString import com.tari.android.wallet.ffi.LogFileObserver import com.tari.android.wallet.ffi.NetAddressString +import com.tari.android.wallet.ffi.TransactionValidationStatus +import com.tari.android.wallet.model.BalanceInfo +import com.tari.android.wallet.model.CancelledTx +import com.tari.android.wallet.model.CompletedTx +import com.tari.android.wallet.model.PendingInboundTx +import com.tari.android.wallet.model.PendingOutboundTx +import com.tari.android.wallet.model.TariContact +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.model.TransactionSendStatus +import com.tari.android.wallet.model.Tx +import com.tari.android.wallet.model.TxId import com.tari.android.wallet.model.fullBase58 -import com.tari.android.wallet.service.seedPhrase.SeedPhraseRepository +import com.tari.android.wallet.model.seedPhrase.SeedPhrase +import com.tari.android.wallet.notification.NotificationHelper +import com.tari.android.wallet.recovery.WalletRestorationState +import com.tari.android.wallet.recovery.WalletRestorationStateHandler +import com.tari.android.wallet.service.baseNode.BaseNodeState +import com.tari.android.wallet.service.baseNode.BaseNodeStateHandler +import com.tari.android.wallet.service.baseNode.BaseNodeSyncState +import com.tari.android.wallet.service.notification.NotificationService import com.tari.android.wallet.service.service.WalletService +import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.tor.TorConfig import com.tari.android.wallet.tor.TorProxyManager import com.tari.android.wallet.tor.TorProxyStateHandler +import com.tari.android.wallet.ui.common.DialogManager +import com.tari.android.wallet.ui.fragment.home.HomeActivity import com.tari.android.wallet.util.Constants -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.util.DebugConfig +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.io.File +import java.math.BigInteger +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap +import java.util.concurrent.CopyOnWriteArraySet +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton @@ -76,57 +120,84 @@ class WalletManager @Inject constructor( private val walletConfig: WalletConfig, private val torManager: TorProxyManager, private val corePrefRepository: CorePrefRepository, - private val baseNodePrefRepository: BaseNodePrefRepository, - private val seedPhraseRepository: SeedPhraseRepository, private val networkPrefRepository: NetworkPrefRepository, private val tariSettingsPrefRepository: TariSettingsPrefRepository, private val securityPrefRepository: SecurityPrefRepository, private val baseNodesManager: BaseNodesManager, private val torConfig: TorConfig, - private val walletStateHandler: WalletStateHandler, private val torProxyStateHandler: TorProxyStateHandler, + private val baseNodeStateHandler: BaseNodeStateHandler, + private val app: TariWalletApplication, + private val notificationHelper: NotificationHelper, + private val notificationService: NotificationService, + private val walletRestorationStateHandler: WalletRestorationStateHandler, + private val walletServiceLauncher: WalletServiceLauncher, + private val dialogManager: DialogManager, @ApplicationScope private val applicationScope: CoroutineScope, -) { +) : OutboundTxNotifier { + + private var atomicInstance = AtomicReference() + var walletInstance: FFIWallet? + get() = atomicInstance.get() + set(value) = atomicInstance.set(value) + val requireWalletInstance: FFIWallet + get() = walletInstance ?: error("Wallet instance is null") + + private val _walletState = MutableStateFlow(WalletState.NotReady) + val walletState = _walletState.asStateFlow() + + private val _walletEvent = EffectChannelFlow() + val walletEvent: Flow = _walletEvent.flow private var logFileObserver: LogFileObserver? = null private val logger get() = Logger.t(WalletManager::class.simpleName) /** - * Start tor and init wallet. + * Maps the validation type to the request id and validation result. This map will be + * initialized at the beginning of each base node validation sequence. + * Validation results will all be null, and will be set as the result callbacks get called. + */ + private val walletValidationStatusMap: ConcurrentMap = ConcurrentHashMap() + + override val outboundTxIdsToBePushNotified = CopyOnWriteArraySet() + + /** + * Debounce for inbound transaction notification. + * TODO don't use rx. Replace with coroutines. */ + private var txReceivedNotificationDelayedAction: Disposable? = null + private var inboundTxEventNotificationTxs = mutableListOf() + + private var txBroadcastRestarted = false + @Synchronized - fun start() { + fun start(seedWords: List?) { torManager.run() + val ffiSeedWords = SeedPhrase.createOrNull(seedWords) + applicationScope.launch { torProxyStateHandler.doOnTorReadyForWallet { - startWallet() + startWallet(ffiSeedWords) } } } - /** - * DeInit the wallet and shutdown Tor. - */ @Synchronized fun stop() { // destroy FFI wallet object - FFIWallet.instance?.destroy() - FFIWallet.instance = null - walletStateHandler.setWalletState(WalletState.NotReady) + walletInstance?.destroy() + walletInstance = null + _walletState.update { WalletState.NotReady } // stop tor proxy torManager.shutdown() - walletStateHandler.setWalletState(WalletState.NotReady) // todo Don't understand why it's twice } fun onWalletStarted() { - walletStateHandler.setWalletState(WalletState.Running) + _walletState.update { WalletState.Running } } - /** - * Instantiates the comms configuration for the wallet. - */ fun getCommsConfig(): FFICommsConfig = FFICommsConfig( publicAddress = NetAddressString(address = "127.0.0.1", port = 39069).toString(), transport = getTorTransport(), @@ -136,31 +207,96 @@ class WalletManager @Inject constructor( safMessageDurationSec = Constants.Wallet.STORE_AND_FORWARD_MESSAGE_DURATION_SEC, ) - private fun startWallet() { - if (walletStateHandler.walletState.value is WalletState.NotReady || walletStateHandler.walletState.value is WalletState.Failed) { + /** + * Syncs the wallet with the base node and validates the wallet + */ + fun syncBaseNode() { + if (!DebugConfig.selectBaseNodeEnabled) { + Logger.e("baseNodeSync: Base node selection is disabled, but syncBaseNode() is called") + } + var currentBaseNode: BaseNodeDto? = baseNodesManager.currentBaseNode ?: return + + applicationScope.launch(Dispatchers.IO) { + doOnWalletRunning { wallet -> + while (currentBaseNode != null) { + try { + currentBaseNode?.let { it -> + logger.i("baseNodeSync: sync with base node ${it.publicKeyHex}::${it.address} started") + val baseNodeKeyFFI = FFIPublicKey(HexString(it.publicKeyHex)) + val addBaseNodeResult = wallet.addBaseNodePeer(baseNodeKeyFFI, it.address) + baseNodeKeyFFI.destroy() + logger.i("baseNodeSync:addBaseNodePeer ${if (addBaseNodeResult) "success" else "failed"}") + + validateWallet() + } + break + } catch (e: Throwable) { + logger.i("baseNodeSync:error connecting to base node $currentBaseNode with an error: ${e.message}") + currentBaseNode = baseNodesManager.setNextBaseNode() + } + } + + if (currentBaseNode == null) { + logger.e("baseNodeSync: cannot connect to any base node") + } + } + } + } + + /** + * Starts the wallet recovery process. Returns true if the recovery process was started successfully. + * The recovery process events will be handled in the onWalletRestoration() callback. + */ + fun startRecovery(baseNode: BaseNodeDto, recoveryOutputMessage: String): Boolean { + val baseNodeFFI = FFIPublicKey(HexString(baseNode.publicKeyHex)) + return walletInstance?.startRecovery(baseNodeFFI, recoveryOutputMessage) ?: false + } + + fun onWalletRestored() { + corePrefRepository.onboardingCompleted = true + corePrefRepository.onboardingStarted = true + corePrefRepository.onboardingAuthSetupStarted = true + corePrefRepository.onboardingAuthSetupCompleted = false + corePrefRepository.onboardingDisplayedAtHome = true + corePrefRepository.needToShowRecoverySuccessDialog = true + tariSettingsPrefRepository.isRestoredWallet = true + } + + fun deleteWallet() { + logger.i("Deleting wallet: ${walletInstance?.getWalletAddress()?.fullBase58() ?: "wallet is already null!"}") + walletInstance?.destroy() + walletInstance = null + _walletState.update { WalletState.NotReady } + runOnMain { _walletEvent.send(WalletEvent.OnWalletRemove) } + WalletFileUtil.clearWalletFiles(walletConfig.getWalletFilesDirPath()) + corePrefRepository.clear() + dialogManager.dismissAll() + walletServiceLauncher.stop() + + } + + private fun startWallet(ffiSeedWords: FFISeedWords?) { + if (walletState.value is WalletState.NotReady || walletState.value is WalletState.Failed) { logger.i("Initialize wallet started") - walletStateHandler.setWalletState(WalletState.Initializing) + _walletState.update { WalletState.Initializing } applicationScope.launch { try { - initWallet() - walletStateHandler.setWalletState(WalletState.Started) + initWallet(ffiSeedWords) + _walletState.update { WalletState.Started } logger.i("Wallet was started") } catch (e: Exception) { - val oldCode = walletStateHandler.walletState.value.errorCode + val oldCode = walletState.value.errorCode val newCode = e.safeCastTo()?.error?.code if (oldCode == null || oldCode != newCode) { logger.e(e, "Wallet was failed") } - walletStateHandler.setWalletState(WalletState.Failed(e)) + _walletState.update { WalletState.Failed(e) } } }.start() } } - /** - * Instantiates the Tor transport for the wallet. - */ private fun getTorTransport(): FFITariTransportConfig { val cookieFile = File(torConfig.cookieFilePath) if (!cookieFile.exists()) { @@ -194,7 +330,7 @@ class WalletManager @Inject constructor( */ private fun saveWalletAddressToSharedPrefs() { // set shared preferences values after instantiation - FFIWallet.instance?.getWalletAddress()?.let { ffiTariWalletAddress -> + walletInstance?.getWalletAddress()?.let { ffiTariWalletAddress -> corePrefRepository.walletAddressBase58 = ffiTariWalletAddress.fullBase58() corePrefRepository.emojiId = ffiTariWalletAddress.getEmojiId() ffiTariWalletAddress.destroy() @@ -204,42 +340,372 @@ class WalletManager @Inject constructor( /** * Initializes the wallet and sets the singleton instance in the wallet companion object. */ - private fun initWallet() { - if (FFIWallet.instance == null) { + private fun initWallet(ffiSeedWords: FFISeedWords?) { + if (walletInstance == null) { // store network info in shared preferences if it's a new wallet - val isNewInstallation = !WalletUtil.walletExists(walletConfig) - val wallet = FFIWallet( - sharedPrefsRepository = corePrefRepository, - securityPrefRepository = securityPrefRepository, - seedPhraseRepository = seedPhraseRepository, - networkRepository = networkPrefRepository, + val isNewInstallation = !WalletFileUtil.walletExists(walletConfig) + + val passphrase = securityPrefRepository.databasePassphrase.takeIf { !it.isNullOrEmpty() } + ?: corePrefRepository.generateDatabasePassphrase().also { securityPrefRepository.databasePassphrase = it } + + walletInstance = FFIWallet( + tariNetwork = networkPrefRepository.currentNetwork, commsConfig = getCommsConfig(), logPath = walletConfig.getWalletLogFilePath(), + passphrase = passphrase, + seedWords = ffiSeedWords, + listener = object : FFIWalletListener { + /** + * All the callbacks are called on the FFI thread, so we need to switch to the main thread. + * The app will crash if we try to update the UI from the FFI thread. + */ + override fun onTxReceived(pendingInboundTx: PendingInboundTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxReceived( + tx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)), + ) + ) + postTxNotification(pendingInboundTx) + } + + override fun onTxReplyReceived(pendingOutboundTx: PendingOutboundTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxReplyReceived( + tx = pendingOutboundTx.copy(tariContact = getUserByWalletAddress(pendingOutboundTx.tariContact.walletAddress)), + ) + ) + } + + override fun onTxFinalized(pendingInboundTx: PendingInboundTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxFinalized( + tx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)), + ) + ) + } + + override fun onInboundTxBroadcast(pendingInboundTx: PendingInboundTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.InboundTxBroadcast( + tx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)), + ) + ) + } + + override fun onOutboundTxBroadcast(pendingOutboundTx: PendingOutboundTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.OutboundTxBroadcast( + tx = pendingOutboundTx.copy(tariContact = getUserByWalletAddress(pendingOutboundTx.tariContact.walletAddress)), + ) + ) + } + + override fun onTxMined(completedTx: CompletedTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxMined( + tx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)), + ) + ) + } + + override fun onTxMinedUnconfirmed(completedTx: CompletedTx, confirmationCount: Int) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxMinedUnconfirmed( + tx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)), + confirmationCount = confirmationCount, + ) + ) + } + + override fun onTxFauxConfirmed(completedTx: CompletedTx) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxFauxConfirmed( + tx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)), + ) + ) + } + + override fun onTxFauxUnconfirmed(completedTx: CompletedTx, confirmationCount: Int) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxFauxMinedUnconfirmed( + tx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)), + confirmationCount = confirmationCount, + ) + ) + } + + override fun onDirectSendResult(txId: BigInteger, status: TransactionSendStatus) = runOnMain { + _walletEvent.send(WalletEvent.Tx.DirectSendResult(TxId(txId), status)) + outboundTxIdsToBePushNotified.firstOrNull { it.txId == txId }?.let { + outboundTxIdsToBePushNotified.remove(it) + sendPushNotificationToTxRecipient(it.recipientPublicKeyHex) + } + } + + override fun onTxCancelled(cancelledTx: CancelledTx, rejectionReason: Int) = runOnMain { + _walletEvent.send( + WalletEvent.Tx.TxCancelled( + tx = cancelledTx.copy(tariContact = getUserByWalletAddress(cancelledTx.tariContact.walletAddress)), + ) + ) + + // TODO don't use android components in this class + val currentActivity = app.currentActivity + if (cancelledTx.direction == Tx.Direction.INBOUND + && !(app.isInForeground && currentActivity is HomeActivity && currentActivity.willNotifyAboutNewTx()) + ) { + notificationHelper.postTxCanceledNotification(cancelledTx) + } + } + + override fun onTXOValidationComplete(responseId: BigInteger, status: TransactionValidationStatus) = runOnMain { + checkValidationResult( + type = WalletValidationType.TXO, + responseId = responseId, + isSuccess = status == TransactionValidationStatus.Success, + ) + } + + override fun onTxValidationComplete(responseId: BigInteger, status: TransactionValidationStatus) = runOnMain { + checkValidationResult( + type = WalletValidationType.TX, + responseId = responseId, + isSuccess = status == TransactionValidationStatus.Success, + ) + walletInstance?.let { + if (!txBroadcastRestarted && status == TransactionValidationStatus.Success) { + it.restartTxBroadcast() + txBroadcastRestarted = true + logger.i("baseNodeSync:wallet validation: Transaction broadcast restarted (requestId: $responseId)") + } + } + ?: logger.i("baseNodeSync:wallet validation:error: Transaction broadcast restart failed because wallet instance is null (requestId: $responseId)\"") + } + + override fun onBalanceUpdated(balanceInfo: BalanceInfo) = runOnMain { + EventBus.balanceState.post(balanceInfo) // TODO replace with flow!!! + } + + override fun onConnectivityStatus(status: Int) = runOnMain { + when (ConnectivityStatus.entries[status]) { + ConnectivityStatus.CONNECTING -> { + baseNodeStateHandler.updateState(BaseNodeState.Syncing) + logger.i("baseNodeSync:base nodes state: connecting to ${baseNodesManager.currentBaseNode?.publicKeyHex}") + } + + ConnectivityStatus.ONLINE -> { + baseNodesManager.refreshBaseNodeList(requireWalletInstance) + baseNodeStateHandler.updateState(BaseNodeState.Online) + logger.i("baseNodeSync:base nodes state: connected to ${baseNodesManager.currentBaseNode?.publicKeyHex} ONLINE") + } + + ConnectivityStatus.OFFLINE -> { + val currentBaseNode = baseNodesManager.currentBaseNode + if (DebugConfig.selectBaseNodeEnabled && (currentBaseNode == null || !currentBaseNode.isCustom)) { + baseNodesManager.setNextBaseNode() + syncBaseNode() + } + baseNodeStateHandler.updateState(BaseNodeState.Offline) + logger.i("baseNodeSync:base nodes state: disconnected from ${baseNodesManager.currentBaseNode?.publicKeyHex} OFFLINE") + } + } + } + + override fun onWalletRestoration(state: WalletRestorationState) = runOnMain { + walletRestorationStateHandler.updateState(state) + } + + override fun onWalletScannedHeight(height: Int) = runOnMain { + baseNodesManager.saveWalletScannedHeight(height) + } + + override fun onBaseNodeStateChanged(baseNodeState: FFITariBaseNodeState) = runOnMain { + baseNodesManager.saveBaseNodeState(baseNodeState) + } + } ) - FFIWallet.instance = wallet + if (isNewInstallation) { - FFIWallet.instance?.setKeyValue( + walletInstance?.setKeyValue( key = WalletService.Companion.KeyValueStorageKeys.NETWORK, value = networkPrefRepository.currentNetwork.network.uriComponent, ) } else if (tariSettingsPrefRepository.isRestoredWallet && networkPrefRepository.ffiNetwork == null) { networkPrefRepository.ffiNetwork = try { - Network.from(FFIWallet.instance?.getKeyValue(WalletService.Companion.KeyValueStorageKeys.NETWORK) ?: "") + Network.from(walletInstance?.getKeyValue(WalletService.Companion.KeyValueStorageKeys.NETWORK) ?: "") } catch (exception: Exception) { null } } startLogFileObserver() - baseNodesManager.refreshBaseNodeList() - val currentBaseNode = baseNodePrefRepository.currentBaseNode - if (currentBaseNode != null) { - baseNodesManager.startSync() - } else { - baseNodesManager.setNextBaseNode() - baseNodesManager.startSync() + baseNodesManager.loadBaseNodesFromFFI(requireWalletInstance) + .let { + logger.i( + "baseNodeSync: baseNodeList from FFI: ${ + if (it.isEmpty()) "No base nodes available!!" + else "\n${it.joinToString(separator = "\n")}" + }" + ) + } + + if (DebugConfig.selectBaseNodeEnabled) { + walletInstance?.let { baseNodesManager.refreshBaseNodeList(it) } + ?: error("Wallet instance is null when trying to refresh base node list") + if (baseNodesManager.currentBaseNode == null) { + baseNodesManager.setNextBaseNode() + } } + + validateWallet() + saveWalletAddressToSharedPrefs() } } + + private fun validateWallet() { + applicationScope.launch(Dispatchers.IO) { + doOnWalletRunning { wallet -> + try { + logger.i("baseNodeSync:wallet validation:start Tx and TXO validation") + walletValidationStatusMap.clear() + walletValidationStatusMap[WalletValidationType.TXO] = WalletValidationResult(wallet.startTXOValidation(), null) + walletValidationStatusMap[WalletValidationType.TX] = WalletValidationResult(wallet.startTxValidation(), null) + logger.i( + "baseNodeSync:wallet validation:started Tx and TXO validation with " + + "request keys: ${Gson().toJson(walletValidationStatusMap.map { it.value.requestKey })}" + ) + } catch (e: Throwable) { + logger.i("baseNodeSync:wallet validation:error: ${e.message}") + walletValidationStatusMap.clear() + baseNodeStateHandler.updateSyncState(BaseNodeSyncState.Failed) + } + } + } + } + + private fun postTxNotification(tx: Tx) { + txReceivedNotificationDelayedAction?.dispose() + inboundTxEventNotificationTxs.add(tx) + txReceivedNotificationDelayedAction = + Observable.timer(500, TimeUnit.MILLISECONDS) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe { + // if app is backgrounded, display heads-up notification + val currentActivity = app.currentActivity + if (!app.isInForeground + || currentActivity !is HomeActivity + || !currentActivity.willNotifyAboutNewTx() + ) { + notificationHelper.postCustomLayoutTxNotification(inboundTxEventNotificationTxs.last()) + } + inboundTxEventNotificationTxs.clear() + } + } + + private fun sendPushNotificationToTxRecipient(recipientHex: String) { + walletInstance?.let { wallet -> + val senderHex = wallet.getWalletAddress().notificationHex() + notificationService.notifyRecipient(recipientHex, senderHex, wallet::signMessage) + } ?: logger.i("Wallet instance is null when trying to send push notification to recipient") + } + + private fun checkValidationResult(type: WalletValidationType, responseId: BigInteger, isSuccess: Boolean) { + try { + val currentStatus = walletValidationStatusMap[type] ?: return + if (currentStatus.requestKey != responseId) return + walletValidationStatusMap[type] = WalletValidationResult(currentStatus.requestKey, isSuccess) + logger.i("baseNodeSync:wallet validation:validation result for request $responseId: $type: $isSuccess") + checkBaseNodeSyncCompletion() + } catch (e: Throwable) { + logger.i("baseNodeSync:wallet validation $type for request $responseId failed with an error: ${e.message}") + } + } + + private fun checkBaseNodeSyncCompletion() { + // make a copy of the status map for concurrency protection§ + val statusMapCopy = walletValidationStatusMap.toMap() + // if base node not in sync, then switch to the next base node + // check if any has failed + val failed = statusMapCopy.any { it.value.isSuccess == false } + if (failed) { + walletValidationStatusMap.clear() + val currentBaseNode = baseNodesManager.currentBaseNode + if (DebugConfig.selectBaseNodeEnabled && (currentBaseNode == null || !currentBaseNode.isCustom)) { + baseNodesManager.setNextBaseNode() + syncBaseNode() + } + baseNodeStateHandler.updateSyncState(BaseNodeSyncState.Failed) + return + } + // if any of the results is null, we're still waiting for all callbacks to happen + val inProgress = statusMapCopy.any { it.value.isSuccess == null } + if (inProgress) { + baseNodeStateHandler.updateSyncState(BaseNodeSyncState.Syncing) + return + } + // check if it's successful + val successful = statusMapCopy.all { it.value.isSuccess == true } + if (successful) { + walletValidationStatusMap.clear() + baseNodeStateHandler.updateSyncState(BaseNodeSyncState.Online) + } + // shouldn't ever reach here - no-op + } + + private fun getUserByWalletAddress(address: TariWalletAddress): TariContact { + val contactsFFI = requireWalletInstance.getContacts() + for (i in 0 until contactsFFI.getLength()) { + val contactFFI = contactsFFI.getAt(i) + val walletAddressFFI = contactFFI.getWalletAddress() + val tariContact = if (TariWalletAddress(walletAddressFFI) == address) TariContact(contactFFI) else null + walletAddressFFI.destroy() + contactFFI.destroy() + if (tariContact != null) { + contactsFFI.destroy() + return tariContact + } + } + // destroy native collection + contactsFFI.destroy() + return TariContact(address) + } + + enum class ConnectivityStatus(val value: Int) { + CONNECTING(0), + ONLINE(1), + OFFLINE(2), + } + + data class OutboundTxNotification(val txId: BigInteger, val recipientPublicKeyHex: String) + + enum class WalletValidationType { TXO, TX } + data class WalletValidationResult(val requestKey: BigInteger, val isSuccess: Boolean?) + + private fun runOnMain(block: suspend CoroutineScope.() -> Unit) { + applicationScope.launch(Dispatchers.Main) { block() } + } + + sealed class WalletEvent { + object Tx { + data class TxReceived(val tx: PendingInboundTx) : WalletEvent() + data class TxReplyReceived(val tx: PendingOutboundTx) : WalletEvent() + data class TxFinalized(val tx: PendingInboundTx) : WalletEvent() + data class InboundTxBroadcast(val tx: PendingInboundTx) : WalletEvent() + data class OutboundTxBroadcast(val tx: PendingOutboundTx) : WalletEvent() + data class TxMined(val tx: CompletedTx) : WalletEvent() + data class TxMinedUnconfirmed(val tx: CompletedTx, val confirmationCount: Int) : WalletEvent() + data class TxFauxConfirmed(val tx: CompletedTx) : WalletEvent() + data class TxFauxMinedUnconfirmed(val tx: CompletedTx, val confirmationCount: Int) : WalletEvent() + data class TxCancelled(val tx: CancelledTx) : WalletEvent() + data class DirectSendResult(val txId: TxId, val status: TransactionSendStatus) : WalletEvent() + } + + data object OnWalletRemove : WalletEvent() + } +} + +interface OutboundTxNotifier { + val outboundTxIdsToBePushNotified: CopyOnWriteArraySet } diff --git a/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletStateHandler.kt b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletStateHandler.kt index ab439e986..ae89b5ada 100644 --- a/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletStateHandler.kt +++ b/app/src/main/java/com/tari/android/wallet/application/walletManager/WalletStateHandler.kt @@ -3,61 +3,46 @@ package com.tari.android.wallet.application.walletManager import com.orhanobut.logger.Logger import com.tari.android.wallet.ffi.FFIWallet import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class WalletStateHandler @Inject constructor() { - private val logger - get() = Logger.t(this::class.simpleName) +private val logger + get() = Logger.t(WalletManager::class.simpleName) - private val _walletState = MutableStateFlow(WalletState.NotReady) - val walletState = _walletState.asStateFlow() - - fun setWalletState(state: WalletState) { - _walletState.update { state } - } - - suspend fun doOnWalletStarted(action: suspend (ffiWallet: FFIWallet) -> Unit) = withContext(Dispatchers.IO) { - walletState.firstOrNull { it is WalletState.Started } - ?.let { - action(FFIWallet.instance!!) - } ?: logger.i("Wallet service is not connected") - } +suspend fun WalletManager.doOnWalletStarted(action: suspend (ffiWallet: FFIWallet) -> Unit) = withContext(Dispatchers.IO) { + walletState.firstOrNull { it is WalletState.Started } + ?.let { + action(walletInstance!!) + } ?: logger.i("Wallet service is not connected") +} - suspend fun doOnWalletRunning(action: suspend (ffiWallet: FFIWallet) -> Unit) = withContext(Dispatchers.IO) { - walletState.firstOrNull { it is WalletState.Running } - ?.let { - action(FFIWallet.instance!!) - } ?: logger.i("Wallet service is not connected") - } +suspend fun WalletManager.doOnWalletRunning(action: suspend (ffiWallet: FFIWallet) -> Unit) = withContext(Dispatchers.IO) { + walletState.firstOrNull { it is WalletState.Running } + ?.let { + action(walletInstance!!) + } ?: logger.i("Wallet service is not connected") +} - suspend fun doOnWalletRunningWithValue(action: suspend (ffiWallet: FFIWallet) -> T): T = withContext(Dispatchers.IO) { - walletState.firstOrNull { it is WalletState.Running } - ?.let { - action(FFIWallet.instance!!) - } ?: error("Wallet service is not connected") - } +suspend fun WalletManager.doOnWalletRunningWithValue(action: suspend (ffiWallet: FFIWallet) -> T): T = withContext(Dispatchers.IO) { + walletState.firstOrNull { it is WalletState.Running } + ?.let { + action(walletInstance!!) + } ?: error("Wallet service is not connected") +} - suspend fun doOnWalletFailed(action: suspend (exception: Exception) -> Unit) = withContext(Dispatchers.IO) { - walletState - .debounce(300L) // todo this is a workaround for the issue that the wallet service is not connected yet - .firstOrNull { it is WalletState.Failed } - ?.let { - action((it as WalletState.Failed).exception) - } ?: logger.i("Wallet service is not connected") - } +suspend fun WalletManager.doOnWalletFailed(action: suspend (exception: Exception) -> Unit) = withContext(Dispatchers.IO) { + walletState + .debounce(300L) // todo this is a workaround for the issue that the wallet service is not connected yet + .firstOrNull { it is WalletState.Failed } + ?.let { + action((it as WalletState.Failed).exception) + } ?: logger.i("Wallet service is not connected") +} - suspend fun doOnWalletNotReady(action: suspend () -> Unit) = withContext(Dispatchers.IO) { - walletState.firstOrNull { it is WalletState.NotReady } - ?.let { - action() - } ?: logger.i("Wallet service is not connected") - } +suspend fun WalletManager.doOnWalletNotReady(action: suspend () -> Unit) = withContext(Dispatchers.IO) { + walletState.firstOrNull { it is WalletState.NotReady } + ?.let { + action() + } ?: logger.i("Wallet service is not connected") } diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/CorePrefRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/CorePrefRepository.kt index 3cfa5c706..fab23ff15 100644 --- a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/CorePrefRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/CorePrefRepository.kt @@ -82,10 +82,10 @@ class CorePrefRepository @Inject constructor( const val SURNAME = "tari_wallet_surname_" const val ONBOARDING_STARTED = "tari_wallet_onboarding_started" const val ONBOARDING_AUTH_SETUP_COMPLETED = "tari_wallet_onboarding_auth_setup_completed" - const val ACTION_MENU_SIDE = "tari_wallet_action_menu_side" const val ONBOARDING_AUTH_SETUP_STARTED = "tari_wallet_onboarding_auth_setup_started" const val ONBOARDING_COMPLETED = "tari_wallet_onboarding_completed" const val ONBOARDING_DISPLAYED_AT_HOME = "tari_wallet_onboarding_displayed_at_home" + const val NEED_TO_SHOW_RECOVERY_SUCCESS_DIALOG = "NEED_TO_SHOW_RECOVERY_SUCCESS_DIALOG" const val IS_DATA_CLEARED = "tari_is_data_cleared" } @@ -105,8 +105,6 @@ class CorePrefRepository @Inject constructor( var onboardingAuthSetupCompleted: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.ONBOARDING_AUTH_SETUP_COMPLETED)) - var actionMenuSide: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.ACTION_MENU_SIDE)) - val onboardingAuthWasInterrupted: Boolean get() = onboardingAuthSetupStarted && (!onboardingAuthSetupCompleted || securityPrefRepository.pinCode == null) @@ -115,10 +113,18 @@ class CorePrefRepository @Inject constructor( var onboardingDisplayedAtHome: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.ONBOARDING_DISPLAYED_AT_HOME)) + var needToShowRecoverySuccessDialog: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.NEED_TO_SHOW_RECOVERY_SUCCESS_DIALOG), false) + var isDataCleared: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.IS_DATA_CLEARED), true) val walletAddress: TariWalletAddress - get() = TariWalletAddress.fromBase58(walletAddressBase58!!) + get() = walletAddressBase58?.let { TariWalletAddress.fromBase58(it) } ?: error("Wallet address is not set to shared preferences") + + /** + * Sometimes the wallet address is not set to the shared preferences (e.g. after a wallet removing). + * TODO: Investigate why the app accesses the wallet address when it is not set + */ + fun walletAddressExists(): Boolean = walletAddressBase58 != null fun clear() { baseNodeSharedRepository.clear() diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/backup/BackupPrefRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/backup/BackupPrefRepository.kt index 34c85c002..6ee9133e3 100644 --- a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/backup/BackupPrefRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/backup/BackupPrefRepository.kt @@ -6,13 +6,14 @@ import android.net.Uri import com.dropbox.core.oauth.DbxCredential import com.tari.android.wallet.BuildConfig import com.tari.android.wallet.data.sharedPrefs.CommonPrefRepository +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonDelegate import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonNullableDelegate import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefStringSecuredDelegate import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository import com.tari.android.wallet.data.sharedPrefs.network.formatKey import com.tari.android.wallet.infrastructure.backup.BackupUtxos +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptionDto -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions import com.tari.android.wallet.util.Constants import javax.inject.Inject import javax.inject.Singleton @@ -24,66 +25,96 @@ class BackupPrefRepository @Inject constructor( networkRepository: NetworkPrefRepository ) : CommonPrefRepository(networkRepository) { - var localFileOption: BackupOptionDto? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.localFileOptionsKey), BackupOptionDto::class.java) - - var googleDriveOption: BackupOptionDto? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.googleDriveOptionKey), BackupOptionDto::class.java) - - var dropboxOption: BackupOptionDto? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.dropboxOptionsKey), BackupOptionDto::class.java) - - var dropboxCredential: DbxCredential? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.dropboxCredentialKey), DbxCredential::class.java) - - var backupPassword: String? by SharedPrefStringSecuredDelegate(context, sharedPrefs, this, formatKey(Keys.backupPassword)) - - var localBackupFolderURI: Uri? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.localBackupFolderURI), Uri::class.java) - - var restoredTxs: BackupUtxos? by SharedPrefGsonNullableDelegate(sharedPrefs, this, formatKey(Keys.lastRestoredTxs), BackupUtxos::class.java, null) - - init { - localFileOption = localFileOption ?: BackupOptionDto(BackupOptions.Local) - googleDriveOption = googleDriveOption ?: BackupOptionDto(BackupOptions.Google) - } + private var localFileOption: BackupOptionDto by SharedPrefGsonDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.LOCAL_FILE_OPTIONS_KEY), + type = BackupOptionDto::class.java, + defValue = BackupOptionDto(BackupOption.Local), + ) + + private var googleDriveOption: BackupOptionDto by SharedPrefGsonDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.GOOGLE_DRIVE_OPTION_KEY), + type = BackupOptionDto::class.java, + defValue = BackupOptionDto(BackupOption.Google), + ) + + var dropboxOption: BackupOptionDto by SharedPrefGsonDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.DROPBOX_OPTIONS_KEY), + type = BackupOptionDto::class.java, + defValue = BackupOptionDto(BackupOption.Dropbox), + ) + + var dropboxCredential: DbxCredential? by SharedPrefGsonNullableDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.DROPBOX_CREDENTIAL_KEY), + type = DbxCredential::class.java + ) + + var backupPassword: String? by SharedPrefStringSecuredDelegate( + context = context, + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.BACKUP_PASSWORD), + ) + + var localBackupFolderURI: Uri? by SharedPrefGsonNullableDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.LOCAL_BACKUP_FOLDER_URI), + type = Uri::class.java, + ) + + var restoredTxs: BackupUtxos? by SharedPrefGsonNullableDelegate( + prefs = sharedPrefs, + commonRepository = this, + name = formatKey(Keys.LAST_RESTORED_TXS), + type = BackupUtxos::class.java, + ) val getOptionList: List get() = if (BuildConfig.FLAVOR == Constants.Build.privacyFlavor) { - listOfNotNull(localFileOption).toList() + listOfNotNull(localFileOption) } else { - listOfNotNull(googleDriveOption, dropboxOption).toList() + listOfNotNull(googleDriveOption, dropboxOption) } fun clear() { backupPassword = null localBackupFolderURI = null - localFileOption = BackupOptionDto(BackupOptions.Local) - googleDriveOption = BackupOptionDto(BackupOptions.Google) - dropboxOption = BackupOptionDto(BackupOptions.Dropbox) + localFileOption = BackupOptionDto(BackupOption.Local) + googleDriveOption = BackupOptionDto(BackupOption.Google) + dropboxOption = BackupOptionDto(BackupOption.Dropbox) } fun updateOption(option: BackupOptionDto) { when (option.type) { - BackupOptions.Google -> googleDriveOption = option - BackupOptions.Local -> localFileOption = option - BackupOptions.Dropbox -> dropboxOption = option + BackupOption.Google -> googleDriveOption = option + BackupOption.Local -> localFileOption = option + BackupOption.Dropbox -> dropboxOption = option } } - fun getOptionDto(type: BackupOptions): BackupOptionDto? = when (type) { - BackupOptions.Google -> googleDriveOption - BackupOptions.Local -> localFileOption - BackupOptions.Dropbox -> dropboxOption - } - - object Keys { - const val googleDriveOptionKey = "tari_wallet_google_drive_backup_options" - const val localFileOptionsKey = "tari_wallet_local_file_backup_options" - const val dropboxOptionsKey = "tari_wallet_dropbox_backup_options" - const val dropboxCredentialKey = "tari_wallet_dropbox_credential_key" - const val backupPassword = "tari_wallet_last_next_alarm_time" - const val localBackupFolderURI = "tari_wallet_local_backup_folder_uri" - const val lastBackupDialogShownTime = "last_shown_time_key" - const val lastRestoredTxs = "tari_wallet_restored_txs" + fun getOptionDto(type: BackupOption): BackupOptionDto = when (type) { + BackupOption.Google -> googleDriveOption + BackupOption.Local -> localFileOption + BackupOption.Dropbox -> dropboxOption } companion object { - const val delayTimeInMinutes = 5 + object Keys { + const val GOOGLE_DRIVE_OPTION_KEY = "tari_wallet_google_drive_backup_options" + const val LOCAL_FILE_OPTIONS_KEY = "tari_wallet_local_file_backup_options" + const val DROPBOX_OPTIONS_KEY = "tari_wallet_dropbox_backup_options" + const val DROPBOX_CREDENTIAL_KEY = "tari_wallet_dropbox_credential_key" + const val BACKUP_PASSWORD = "tari_wallet_last_next_alarm_time" + const val LOCAL_BACKUP_FOLDER_URI = "tari_wallet_local_backup_folder_uri" + const val LAST_RESTORED_TXS = "tari_wallet_restored_txs" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/baseNode/BaseNodePrefRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/baseNode/BaseNodePrefRepository.kt index a71d25b56..d64b13052 100644 --- a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/baseNode/BaseNodePrefRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/baseNode/BaseNodePrefRepository.kt @@ -34,16 +34,10 @@ package com.tari.android.wallet.data.sharedPrefs.baseNode import android.content.SharedPreferences import com.tari.android.wallet.data.sharedPrefs.CommonPrefRepository -import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBigIntegerDelegate -import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBooleanNullableDelegate import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonDelegate import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonNullableDelegate -import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefIntDelegate import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository import com.tari.android.wallet.data.sharedPrefs.network.formatKey -import com.tari.android.wallet.event.EventBus -import com.tari.android.wallet.service.baseNode.BaseNodeState -import java.math.BigInteger import javax.inject.Inject import javax.inject.Singleton @@ -56,8 +50,6 @@ class BaseNodePrefRepository @Inject constructor( private object Key { const val CURRENT_BASE_NODE = "tari_wallet_current_base_node" const val USER_BASE_NODE_LIST = "tari_wallet_user_base_nodes" - const val BASE_NODE_STATE = "tari_wallet_user_base_node_state" - const val BASE_NODE_LAST_SYNC_RESULT = "tari_wallet_base_node_last_sync_result" const val FFI_BASE_NODE_LIST = "FFI_BASE_NODE_LIST" } @@ -83,31 +75,7 @@ class BaseNodePrefRepository @Inject constructor( defValue = BaseNodeList(), ) - var baseNodeLastSyncResult: Boolean? by SharedPrefBooleanNullableDelegate( - prefs = sharedPrefs, - commonRepository = this, - name = Key.BASE_NODE_LAST_SYNC_RESULT, - ) - - // ordinal value of BaseNodeState enum class - private var baseNodeStateOrdinal: Int by SharedPrefIntDelegate( - prefs = sharedPrefs, - commonRepository = this, - name = Key.BASE_NODE_STATE, - defValue = BaseNodeState.Syncing.ordinal, - ) - var baseNodeState: BaseNodeState - get() = BaseNodeState.get(baseNodeStateOrdinal) - set(value) { - baseNodeStateOrdinal = value.ordinal - } - - init { - EventBus.baseNodeState.post(baseNodeState) - } - fun clear() { - baseNodeLastSyncResult = null currentBaseNode = null userBaseNodes = BaseNodeList() ffiBaseNodes = BaseNodeList() diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/tor/TorBridgeConfiguration.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/tor/TorBridgeConfiguration.kt index 914201a40..a3592f4a6 100644 --- a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/tor/TorBridgeConfiguration.kt +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/tor/TorBridgeConfiguration.kt @@ -1,5 +1,9 @@ package com.tari.android.wallet.data.sharedPrefs.tor +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class TorBridgeConfiguration( val transportTechnology: String, val ip: String, @@ -7,7 +11,7 @@ data class TorBridgeConfiguration( val fingerprint: String, val certificate: String = "", val iatMode: String = "" -) { +) : Parcelable { override fun toString(): String = ("$transportTechnology $ip:$port $fingerprint ${if (certificate.isNotBlank()) "cert=$certificate" else ""} " + if (iatMode.isNotBlank()) "iat-mode=$iatMode" else "").trim() } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt b/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt index 84710896d..f24a590c8 100644 --- a/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt +++ b/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt @@ -34,8 +34,8 @@ package com.tari.android.wallet.di import android.content.ClipboardManager import com.tari.android.wallet.application.TariWalletApplication -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager +import com.tari.android.wallet.notification.NotificationBroadcastReceiver import com.tari.android.wallet.service.service.WalletService import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.component.clipboardController.WalletAddressViewModel @@ -60,13 +60,13 @@ import com.tari.android.wallet.ui.fragment.onboarding.inroduction.IntroductionVi import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthViewModel import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeViewModel import com.tari.android.wallet.ui.fragment.profile.WalletInfoViewModel -import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity -import com.tari.android.wallet.ui.fragment.qr.QRScannerViewModel +import com.tari.android.wallet.ui.fragment.qr.QrScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerViewModel import com.tari.android.wallet.ui.fragment.restore.activity.WalletRestoreActivity import com.tari.android.wallet.ui.fragment.restore.chooseRestoreOption.ChooseRestoreOptionViewModel import com.tari.android.wallet.ui.fragment.restore.enterRestorationPassword.EnterRestorationPasswordViewModel import com.tari.android.wallet.ui.fragment.restore.inputSeedWords.InputSeedWordsViewModel -import com.tari.android.wallet.ui.fragment.restore.walletRestoringFromSeedWords.WalletRestoringFromSeedWordsViewModel +import com.tari.android.wallet.ui.fragment.restore.walletRestoring.WalletRestoringViewModel import com.tari.android.wallet.ui.fragment.send.addAmount.AddAmountViewModel import com.tari.android.wallet.ui.fragment.send.addNote.AddNoteViewModel import com.tari.android.wallet.ui.fragment.send.addNote.gif.ChooseGIFDialogFragment @@ -134,7 +134,7 @@ interface ApplicationComponent { fun inject(activity: OnboardingFlowActivity) fun inject(activity: AuthActivity) fun inject(activity: HomeActivity) - fun inject(activity: QRScannerActivity) + fun inject(activity: QrScannerActivity) fun inject(activity: WalletRestoreActivity) fun inject(fragment: ChooseGIFDialogFragment) @@ -148,7 +148,7 @@ interface ApplicationComponent { fun inject(viewModel: ConnectionIndicatorViewModel) fun inject(viewModel: ChooseRestoreOptionViewModel) fun inject(viewModel: EnterRestorationPasswordViewModel) - fun inject(viewModel: WalletRestoringFromSeedWordsViewModel) + fun inject(viewModel: WalletRestoringViewModel) fun inject(viewModel: InputSeedWordsViewModel) fun inject(viewModel: VerifySeedPhraseViewModel) fun inject(viewModel: BackupSettingsViewModel) @@ -162,7 +162,6 @@ interface ApplicationComponent { fun inject(viewModel: AddAmountViewModel) fun inject(viewModel: TorBridgesSelectionViewModel) fun inject(viewModel: CustomTorBridgesViewModel) - fun inject(viewModel: DeeplinkViewModel) fun inject(viewModel: LocalAuthViewModel) fun inject(viewModel: CreateWalletViewModel) fun inject(viewModel: IntroductionViewModel) @@ -196,12 +195,14 @@ interface ApplicationComponent { fun inject(viewModel: TransactionHistoryViewModel) fun inject(viewModel: BluetoothSettingsViewModel) fun inject(viewModel: WalletAddressViewModel) - fun inject(viewModel: QRScannerViewModel) + fun inject(viewModel: QrScannerViewModel) fun inject(viewModel: ChatListViewModel) fun inject(viewModel: ChatDetailsViewModel) fun inject(viewModel: DataCollectionViewModel) fun inject(viewModel: EnterPinCodeViewModel) fun inject(viewModel: ChangeBiometricsViewModel) + fun inject(notificationBroadcastReceiver: NotificationBroadcastReceiver) + fun getClipboardManager(): ClipboardManager } diff --git a/app/src/main/java/com/tari/android/wallet/event/Event.kt b/app/src/main/java/com/tari/android/wallet/event/Event.kt index 663a0967c..99a9b0e82 100644 --- a/app/src/main/java/com/tari/android/wallet/event/Event.kt +++ b/app/src/main/java/com/tari/android/wallet/event/Event.kt @@ -32,11 +32,6 @@ */ package com.tari.android.wallet.event -import com.tari.android.wallet.model.CancelledTx -import com.tari.android.wallet.model.CompletedTx -import com.tari.android.wallet.model.PendingInboundTx -import com.tari.android.wallet.model.PendingOutboundTx -import com.tari.android.wallet.model.TransactionSendStatus import com.tari.android.wallet.model.TxId import com.tari.android.wallet.ui.fragment.send.finalize.TxFailureReason @@ -45,6 +40,8 @@ import com.tari.android.wallet.ui.fragment.send.finalize.TxFailureReason */ object Event { + // TODO use WalletManager.WalletEvent instead of these EventBus events + object App { class AppBackgrounded class AppForegrounded @@ -55,17 +52,7 @@ object Event { */ object Transaction { object Updated - data class TxReceived(val tx: PendingInboundTx) - data class TxReplyReceived(val tx: PendingOutboundTx) - data class TxFinalized(val tx: PendingInboundTx) - data class InboundTxBroadcast(val tx: PendingInboundTx) - data class OutboundTxBroadcast(val tx: PendingOutboundTx) - data class TxMined(val tx: CompletedTx) - data class TxMinedUnconfirmed(val tx: CompletedTx) - data class TxFauxConfirmed(val tx: CompletedTx) - data class TxFauxMinedUnconfirmed(val tx: CompletedTx) - data class TxCancelled(val tx: CancelledTx) - data class DirectSendResult(val txId: TxId, val status: TransactionSendStatus) + data class TxSendSuccessful(val txId: TxId) data class TxSendFailed(val failureReason: TxFailureReason) } diff --git a/app/src/main/java/com/tari/android/wallet/event/EventBus.kt b/app/src/main/java/com/tari/android/wallet/event/EventBus.kt index 9ee6e959c..c79a3b024 100644 --- a/app/src/main/java/com/tari/android/wallet/event/EventBus.kt +++ b/app/src/main/java/com/tari/android/wallet/event/EventBus.kt @@ -34,10 +34,6 @@ package com.tari.android.wallet.event import com.tari.android.wallet.infrastructure.backup.BackupsState import com.tari.android.wallet.model.BalanceInfo -import com.tari.android.wallet.model.recovery.WalletRestorationResult -import com.tari.android.wallet.network.NetworkConnectionState -import com.tari.android.wallet.service.baseNode.BaseNodeState -import com.tari.android.wallet.service.baseNode.BaseNodeSyncState /** * Event bus for the pub/sub model. @@ -52,36 +48,15 @@ object EventBus : GeneralEventBus() { val balanceState = BehaviorEventBus() - val networkConnectionState = BehaviorEventBus() - val backupState = BehaviorEventBus() - val baseNodeState = BehaviorEventBus() - - val baseNodeSyncState = BehaviorEventBus() - - val walletRestorationState = BehaviorEventBus() - - init { - baseNodeSyncState.post(BaseNodeSyncState.Syncing) - } - fun unsubscribeAll(subscriber: Any) { EventBus.unsubscribe(subscriber) - networkConnectionState.unsubscribe(subscriber) backupState.unsubscribe(subscriber) - baseNodeState.unsubscribe(subscriber) - baseNodeSyncState.unsubscribe(subscriber) - walletRestorationState.unsubscribe(subscriber) } override fun clear() { super.clear() - networkConnectionState.clear() backupState.clear() - baseNodeState.clear() - baseNodeSyncState.clear() - walletRestorationState.clear() } } - diff --git a/app/src/main/java/com/tari/android/wallet/extension/ViewModelExtension.kt b/app/src/main/java/com/tari/android/wallet/extension/ViewModelExtension.kt index 771b8c058..ff67fa9a7 100644 --- a/app/src/main/java/com/tari/android/wallet/extension/ViewModelExtension.kt +++ b/app/src/main/java/com/tari/android/wallet/extension/ViewModelExtension.kt @@ -56,10 +56,6 @@ fun CommonView<*, *>.observe(liveData: LiveData, action: (data: T) -> Uni } } -fun CommonView<*, *>.observeOnLoad(liveData: LiveData) { - observe(liveData) { } -} - fun LiveData.debounce(duration: Long = 1000L) = MediatorLiveData().also { mld -> val source = this val handler = Handler(Looper.getMainLooper()) @@ -75,17 +71,17 @@ fun LiveData.debounce(duration: Long = 1000L) = MediatorLiveData().als } fun ViewModel.launchOnIo(action: suspend () -> Unit): Job { - return viewModelScope.launch { - withContext(Dispatchers.IO) { - action() - } + return viewModelScope.launch(Dispatchers.IO) { + action() } } fun ViewModel.launchOnMain(action: suspend () -> Unit): Job { - return viewModelScope.launch { - withContext(Dispatchers.Main) { - action() - } + return viewModelScope.launch(Dispatchers.Main) { + action() } -} \ No newline at end of file +} + +suspend fun switchToIo(action: suspend () -> Unit) = withContext(Dispatchers.IO) { action() } + +suspend fun switchToMain(action: suspend () -> Unit) = withContext(Dispatchers.Main) { action() } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFIBase.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFIBase.kt index 46cf231d8..50bcca9a9 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFIBase.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFIBase.kt @@ -32,9 +32,6 @@ */ package com.tari.android.wallet.ffi -import com.orhanobut.logger.Logger -import com.orhanobut.logger.Printer - typealias FFIPointer = Long const val nullptr = 0L @@ -50,12 +47,6 @@ abstract class FFIBase { var pointer = nullptr protected set - companion object { - @JvmStatic - protected val logger: Printer - get() = Logger.t(this::class.simpleName) - } - abstract fun destroy() protected fun finalize() { diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFICommsConfig.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFICommsConfig.kt index cb09bc72d..b3ff42612 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFICommsConfig.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFICommsConfig.kt @@ -32,6 +32,7 @@ */ package com.tari.android.wallet.ffi +import com.orhanobut.logger.Logger import java.io.File /** @@ -41,6 +42,9 @@ import java.io.File */ class FFICommsConfig() : FFIBase() { + private val logger + get() = Logger.t(FFICommsConfig::class.simpleName) + private external fun jniCreate( publicAddress: String, transport: FFITariTransportConfig, diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFISeedWords.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFISeedWords.kt index 6002cff20..e92b1f675 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFISeedWords.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFISeedWords.kt @@ -42,6 +42,7 @@ import com.tari.android.wallet.model.seedPhrase.SeedWordsWordPushResult class FFISeedWords() : FFIBase() { private external fun jniCreate() + private external fun jniFromBase58(cypher: String, passphrase: String, libError: FFIError) private external fun jniPushWord(word: String, libError: FFIError): Int private external fun jniGetLength(libError: FFIError): Int private external fun jniGetAt(index: Int, libError: FFIError): String @@ -53,6 +54,10 @@ class FFISeedWords() : FFIBase() { jniCreate() } + constructor(base58: Base58, passphrase: String) : this() { + runWithError { jniFromBase58(base58, passphrase, it) } + } + constructor(pointer: FFIPointer) : this() { if (pointer.isNull()) error("Pointer must not be null") this.pointer = pointer diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt index 6ab45eb32..c9d655ec1 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt @@ -32,10 +32,9 @@ */ package com.tari.android.wallet.ffi +import com.orhanobut.logger.Logger import com.tari.android.wallet.BuildConfig -import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository -import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository -import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository +import com.tari.android.wallet.data.sharedPrefs.network.TariNetwork import com.tari.android.wallet.model.BalanceInfo import com.tari.android.wallet.model.CancelledTx import com.tari.android.wallet.model.CompletedTx @@ -47,14 +46,8 @@ import com.tari.android.wallet.model.TariUnblindedOutput import com.tari.android.wallet.model.TariVector import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.Tx -import com.tari.android.wallet.model.recovery.WalletRestorationResult -import com.tari.android.wallet.service.seedPhrase.SeedPhraseRepository -import com.tari.android.wallet.util.Constants -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import com.tari.android.wallet.recovery.WalletRestorationState import java.math.BigInteger -import java.util.concurrent.atomic.AtomicReference /** * Wallet wrapper. @@ -62,42 +55,19 @@ import java.util.concurrent.atomic.AtomicReference * @author The Tari Development Team */ -@Suppress("MemberVisibilityCanBePrivate") class FFIWallet( - private val sharedPrefsRepository: CorePrefRepository, - private val securityPrefRepository: SecurityPrefRepository, - private val seedPhraseRepository: SeedPhraseRepository, - private val networkRepository: NetworkPrefRepository, - private val commsConfig: FFICommsConfig, - private val logPath: String + private val listener: FFIWalletListener, ) : FFIBase() { - private val coroutineContext = Job() - private var localScope = CoroutineScope(coroutineContext) - - // values for the wallet initialization - private val logVerbosity: Int = if (BuildConfig.BUILD_TYPE == "debug") 11 else 4 - private val isDnsSecureOn = false + private val logger + get() = Logger.t(FFIWallet::class.simpleName) companion object { - private var atomicInstance = AtomicReference() - var instance: FFIWallet? - get() = atomicInstance.get() - set(value) = atomicInstance.set(value) - - fun getOrNull(block: (FFIWallet) -> T): T? { - return instance.takeIf { it != null }?.let { wallet -> - try { - block(wallet) - } catch (e: Exception) { - logger.e("FFIWallet block failed with exception: $e") - null - } - } ?: run { - logger.i("Trying to access FFIWallet instance before it is initialized.") - null - } - } + // values for the wallet initialization + private val LOG_VERBOSITY: Int = if (BuildConfig.BUILD_TYPE == "debug") 11 else 4 + private const val IS_DNS_SECURE_ON = false + private const val MAX_NUMBER_OF_ROLLING_LOG_FILES = 2 + private const val ROLLING_LOG_FILE_MAX_SIZE_BYTES = 10 * 1024 * 1024 } private external fun jniCreate( @@ -149,35 +119,20 @@ class FFIWallet( ) private external fun jniGetBalance(libError: FFIError): FFIPointer - private external fun jniLogMessage(message: String, libError: FFIError) - private external fun jniGetWalletAddress(libError: FFIError): FFIPointer - private external fun jniGetContacts(libError: FFIError): FFIPointer - private external fun jniAddUpdateContact(contactPtr: FFIContact, libError: FFIError): Boolean - private external fun jniRemoveContact(contactPtr: FFIContact, libError: FFIError): Boolean - private external fun jniGetCompletedTxs(libError: FFIError): FFIPointer - private external fun jniGetCancelledTxs(libError: FFIError): FFIPointer - private external fun jniGetCompletedTxById(id: String, libError: FFIError): FFIPointer - private external fun jniGetCancelledTxById(id: String, libError: FFIError): FFIPointer - private external fun jniGetPendingOutboundTxs(libError: FFIError): FFIPointer - private external fun jniGetPendingOutboundTxById(id: String, libError: FFIError): FFIPointer - private external fun jniGetPendingInboundTxs(libError: FFIError): FFIPointer - private external fun jniGetPendingInboundTxById(id: String, libError: FFIError): FFIPointer - private external fun jniCancelPendingTx(id: String, libError: FFIError): Boolean - private external fun jniSendTx( publicKeyPtr: FFITariWalletAddress, amount: String, @@ -189,61 +144,37 @@ class FFIWallet( ): ByteArray private external fun jniSignMessage(message: String, libError: FFIError): String - private external fun jniVerifyMessageSignature(publicKeyPtr: FFIPublicKey, message: String, signature: String, libError: FFIError): Boolean - private external fun jniGetBaseNodePeers(libError: FFIError): FFIPointer - private external fun jniAddBaseNodePeer(publicKey: FFIPublicKey, address: String, libError: FFIError): Boolean - private external fun jniStartTXOValidation(libError: FFIError): ByteArray - private external fun jniStartTxValidation(libError: FFIError): ByteArray - private external fun jniRestartTxBroadcast(libError: FFIError): ByteArray - private external fun jniPowerModeNormal(libError: FFIError) - private external fun jniPowerModeLow(libError: FFIError) - private external fun jniGetSeedWords(libError: FFIError): FFIPointer - private external fun jniSetKeyValue(key: String, value: String, libError: FFIError): Boolean - private external fun jniGetKeyValue(key: String, libError: FFIError): String - private external fun jniRemoveKeyValue(key: String, libError: FFIError): Boolean - private external fun jniGetConfirmations(libError: FFIError): ByteArray - private external fun jniSetConfirmations(number: String, libError: FFIError) - private external fun jniEstimateTxFee(amount: String, gramFee: String, kernelCount: String, outputCount: String, libError: FFIError): ByteArray - private external fun jniStartRecovery( - base_node_public_key: FFIPublicKey, + baseNodePublicKey: FFIPublicKey, callback: String, - callback_sig: String, + callbackSig: String, recoveryOutputMessage: String, libError: FFIError ): Boolean private external fun jniWalletGetFeePerGramStats(count: Int, libError: FFIError): FFIPointer - private external fun jniGetUtxos(page: Int, pageSize: Int, sorting: Int, dustThreshold: Long, libError: FFIError): FFIPointer - private external fun jniGetAllUtxos(libError: FFIError): FFIPointer - private external fun jniJoinUtxos(commitments: Array, feePerGram: String, libError: FFIError): FFIPointer - private external fun jniSplitUtxos(commitments: Array, splitCount: String, feePerGram: String, libError: FFIError): FFIPointer - private external fun jniPreviewJoinUtxos(commitments: Array, feePerGram: String, libError: FFIError): FFIPointer - private external fun jniPreviewSplitUtxos(commitments: Array, splitCount: String, feePerGram: String, libError: FFIError): FFIPointer - private external fun jniWalletGetUnspentOutputs(libError: FFIError): FFIPointer - private external fun jniImportExternalUtxoAsNonRewindable( output: FFITariUnblindedOutput, sourceAddress: FFITariWalletAddress, @@ -253,39 +184,29 @@ class FFIWallet( private external fun jniDestroy() - - var listener: FFIWalletListener? = null - - // this acts as a constructor would for a normal class since constructors are not allowed for - // singletons - init { - if (pointer == nullptr) { // so it can only be assigned once for the singleton - init() - } - } - - private fun init() { + constructor( + tariNetwork: TariNetwork, + commsConfig: FFICommsConfig, + logPath: String, + passphrase: String, + seedWords: FFISeedWords?, + listener: FFIWalletListener, + ) : this(listener) { val error = FFIError() logger.i("Pre jniCreate") - var passphrase = securityPrefRepository.databasePassphrase - if (passphrase.isNullOrEmpty()) { - passphrase = sharedPrefsRepository.generateDatabasePassphrase() - securityPrefRepository.databasePassphrase = passphrase - } - try { jniCreate( commsConfig = commsConfig, logPath = logPath, - logVerbosity = logVerbosity, - maxNumberOfRollingLogFiles = Constants.Wallet.MAX_NUMBER_OF_ROLLING_LOG_FILES, - rollingLogFileMaxSizeBytes = Constants.Wallet.ROLLING_LOG_FILE_MAX_SIZE_BYTES, + logVerbosity = LOG_VERBOSITY, + maxNumberOfRollingLogFiles = MAX_NUMBER_OF_ROLLING_LOG_FILES, + rollingLogFileMaxSizeBytes = ROLLING_LOG_FILE_MAX_SIZE_BYTES, passphrase = passphrase, - network = networkRepository.currentNetwork.network.uriComponent, - seedWords = seedPhraseRepository.getPhrase()?.ffiSeedWords, - dnsPeer = networkRepository.currentNetwork.dnsPeer, - isDnsSecureOn = isDnsSecureOn, + network = tariNetwork.network.uriComponent, + seedWords = seedWords, + dnsPeer = tariNetwork.dnsPeer, + isDnsSecureOn = IS_DNS_SECURE_ON, this::onTxReceived.name, "(J)V", this::onTxReplyReceived.name, "(J)V", this::onTxFinalized.name, "(J)V", @@ -310,7 +231,7 @@ class FFIWallet( throw e } - logger.i("Post jniCreate with code: %d.", error.code) + logger.i("Post jniCreate with code: ${error.code}.") throwIf(error) } @@ -351,133 +272,6 @@ class FFIWallet( fun cancelPendingTx(id: BigInteger): Boolean = runWithError { jniCancelPendingTx(id.toString(), it) } - fun onTxReceived(pendingInboundTxPtr: FFIPointer) { - val tx = FFIPendingInboundTx(pendingInboundTxPtr) - logger.i("Tx received ${tx.getId()}") - val pendingTx = PendingInboundTx(tx) - localScope.launch { listener?.onTxReceived(pendingTx) } - } - - /** - * This callback function cannot be private due to JNI behaviour - */ - fun onTxReplyReceived(txPointer: FFIPointer) { - val tx = FFICompletedTx(txPointer) - logger.i("Tx reply received ${tx.getId()}") - val pendingOutboundTx = PendingOutboundTx(tx) - localScope.launch { listener?.onTxReplyReceived(pendingOutboundTx) } - } - - fun onTxFinalized(completedTx: FFIPointer) { - val tx = FFICompletedTx(completedTx) - logger.i("Tx finalized ${tx.getId()}") - val pendingInboundTx = PendingInboundTx(tx) - localScope.launch { listener?.onTxFinalized(pendingInboundTx) } - } - - fun onTxBroadcast(completedTxPtr: FFIPointer) { - val tx = FFICompletedTx(completedTxPtr) - logger.i("Tx broadcast ${tx.getId()}") - when (tx.getDirection()) { - Tx.Direction.INBOUND -> { - val pendingInboundTx = PendingInboundTx(tx) - localScope.launch { listener?.onInboundTxBroadcast(pendingInboundTx) } - } - - Tx.Direction.OUTBOUND -> { - val pendingOutboundTx = PendingOutboundTx(tx) - localScope.launch { listener?.onOutboundTxBroadcast(pendingOutboundTx) } - } - } - } - - fun onTxMined(completedTxPtr: FFIPointer) { - val completed = CompletedTx(completedTxPtr) - logger.i("Tx mined & confirmed ${completed.id}") - localScope.launch { listener?.onTxMined(completed) } - } - - fun onTxMinedUnconfirmed(completedTxPtr: FFIPointer, confirmationCountBytes: ByteArray) { - val confirmationCount = BigInteger(1, confirmationCountBytes).toInt() - val completed = CompletedTx(completedTxPtr) - logger.i("Tx mined & unconfirmed ${completed.id} $confirmationCount") - localScope.launch { listener?.onTxMinedUnconfirmed(completed, confirmationCount) } - } - - fun onTxFauxConfirmed(completedTxPtr: FFIPointer) { - val completed = CompletedTx(completedTxPtr) - logger.i("Tx faux confirmed ${completed.id}") - localScope.launch { listener?.onTxMined(completed) } - } - - fun onBaseNodeStatus(baseNodeStatePointer: FFIPointer) { - val baseNodeState = FFITariBaseNodeState(baseNodeStatePointer) - logger.i("Base node state updated (height of the longest chain is ${baseNodeState.getHeightOfLongestChain()})") - localScope.launch { listener?.onBaseNodeStateChanged(baseNodeState) } - } - - fun onTxFauxUnconfirmed(completedTxPtr: FFIPointer, confirmationCountBytes: ByteArray) { - val confirmationCount = BigInteger(1, confirmationCountBytes).toInt() - val completed = CompletedTx(completedTxPtr) - logger.i("Tx faux unconfirmed ${completed.id}") - localScope.launch { listener?.onTxMinedUnconfirmed(completed, confirmationCount) } - } - - fun onDirectSendResult(bytes: ByteArray, pointer: FFIPointer) { - val txId = BigInteger(1, bytes) - logger.i("Tx direct send result $txId") - localScope.launch { listener?.onDirectSendResult(txId, FFITransactionSendStatus(pointer).getStatus()) } - } - - fun onTxCancelled(completedTx: FFIPointer, rejectionReason: ByteArray) { - val rejectionReasonInt = BigInteger(1, rejectionReason).toInt() - val tx = FFICompletedTx(completedTx) - logger.i("Tx cancelled ${tx.getId()}") - - if (tx.getDirection() == Tx.Direction.OUTBOUND) { - localScope.launch { listener?.onTxCancelled(CancelledTx(tx), rejectionReasonInt) } - } - } - - fun onConnectivityStatus(bytes: ByteArray) { - val connectivityStatus = BigInteger(1, bytes) - localScope.launch { listener?.onConnectivityStatus(connectivityStatus.toInt()) } - logger.i("ConnectivityStatus is [$connectivityStatus]") - } - - fun onWalletScannedHeight(bytes: ByteArray) { - val height = BigInteger(1, bytes) - localScope.launch { listener?.onWalletScannedHeight(height.toInt()) } - logger.i("Wallet scanned height is [$height]") - } - - fun onBalanceUpdated(ptr: FFIPointer) { - logger.i("Balance Updated") - val balance = FFIBalance(ptr).runWithDestroy { BalanceInfo(it.getAvailable(), it.getIncoming(), it.getOutgoing(), it.getTimeLocked()) } - localScope.launch { listener?.onBalanceUpdated(balance) } - } - - fun onTXOValidationComplete(bytes: ByteArray, statusBytes: ByteArray) { - val requestId = BigInteger(1, bytes) - val statusInteger = BigInteger(1, statusBytes).toInt() - val status = TransactionValidationStatus.entries.firstOrNull { it.value == statusInteger } ?: return - logger.i("TXO validation [$requestId] complete. Result: $status") - localScope.launch { listener?.onTXOValidationComplete(requestId, status) } - } - - fun onTxValidationComplete(requestIdBytes: ByteArray, statusBytes: ByteArray) { - val requestId = BigInteger(1, requestIdBytes) - val statusInteger = BigInteger(1, statusBytes).toInt() - val status = TransactionValidationStatus.entries.firstOrNull { it.value == statusInteger } ?: return - logger.i("Tx validation [$requestId] complete. Result: $status") - localScope.launch { listener?.onTxValidationComplete(requestId, status) } - } - - @Suppress("MemberVisibilityCanBePrivate", "UNUSED_PARAMETER") - fun onContactLivenessDataUpdated(livenessUpdate: FFIPointer) { - logger.i("OnContactLivenessDataUpdated") - } - fun estimateTxFee(amount: BigInteger, gramFee: BigInteger, kernelCount: BigInteger, outputCount: BigInteger): BigInteger = runWithError { BigInteger(1, jniEstimateTxFee(amount.toString(), gramFee.toString(), kernelCount.toString(), outputCount.toString(), it)) } @@ -573,15 +367,150 @@ class FFIWallet( } } - fun onWalletRecovery(event: Int, firstArg: ByteArray, secondArg: ByteArray) { - val result = WalletRestorationResult.create(event, firstArg, secondArg) - logger.i("Wallet restored with $result") - localScope.launch { listener?.onWalletRestoration(result) } + private fun onWalletRecovery(event: Int, firstArg: ByteArray, secondArg: ByteArray) { + val state = WalletRestorationState.create(event, firstArg, secondArg) + logger.i( + "Wallet restoration: ${ + when (state) { + is WalletRestorationState.ConnectingToBaseNode -> "Connecting to base node" + is WalletRestorationState.ConnectedToBaseNode -> "Connected to base node" + is WalletRestorationState.ConnectionToBaseNodeFailed -> "Connection to base node failed: ${state.retryCount}/${state.retryLimit}" + is WalletRestorationState.Progress -> "Progress: ${state.currentBlock}/${state.numberOfBlocks}" + is WalletRestorationState.Completed -> "Completed: ${state.numberOfUTXO} UTXOs, ${state.microTari.size} MicroTari" + is WalletRestorationState.ScanningRoundFailed -> "Scanning round failed: ${state.retryCount}/${state.retryLimit}" + is WalletRestorationState.RecoveryFailed -> "Recovery failed" + } + }" + ) + listener.onWalletRestoration(state) } override fun destroy() { - listener = null jniDestroy() } -} + /* FFI wallet callbacks */ + + private fun onTxReceived(pendingInboundTxPtr: FFIPointer) { + val tx = FFIPendingInboundTx(pendingInboundTxPtr) + logger.i("Tx received ${tx.getId()}") + val pendingTx = PendingInboundTx(tx) + listener.onTxReceived(pendingTx) + } + + private fun onTxReplyReceived(txPointer: FFIPointer) { + val tx = FFICompletedTx(txPointer) + logger.i("Tx reply received ${tx.getId()}") + val pendingOutboundTx = PendingOutboundTx(tx) + listener.onTxReplyReceived(pendingOutboundTx) + } + + private fun onTxFinalized(completedTx: FFIPointer) { + val tx = FFICompletedTx(completedTx) + logger.i("Tx finalized ${tx.getId()}") + val pendingInboundTx = PendingInboundTx(tx) + listener.onTxFinalized(pendingInboundTx) + } + + private fun onTxBroadcast(completedTxPtr: FFIPointer) { + val tx = FFICompletedTx(completedTxPtr) + logger.i("Tx broadcast ${tx.getId()}") + when (tx.getDirection()) { + Tx.Direction.INBOUND -> { + val pendingInboundTx = PendingInboundTx(tx) + listener.onInboundTxBroadcast(pendingInboundTx) + } + + Tx.Direction.OUTBOUND -> { + val pendingOutboundTx = PendingOutboundTx(tx) + listener.onOutboundTxBroadcast(pendingOutboundTx) + } + } + } + + private fun onTxMined(completedTxPtr: FFIPointer) { + val completed = CompletedTx(completedTxPtr) + logger.i("Tx mined & confirmed ${completed.id}") + listener.onTxMined(completed) + } + + private fun onTxMinedUnconfirmed(completedTxPtr: FFIPointer, confirmationCountBytes: ByteArray) { + val confirmationCount = BigInteger(1, confirmationCountBytes).toInt() + val completed = CompletedTx(completedTxPtr) + logger.i("Tx mined & unconfirmed ${completed.id} $confirmationCount") + listener.onTxMinedUnconfirmed(completed, confirmationCount) + } + + private fun onTxFauxConfirmed(completedTxPtr: FFIPointer) { + val completed = CompletedTx(completedTxPtr) + logger.i("Tx faux confirmed ${completed.id}") + listener.onTxMined(completed) + } + + private fun onTxFauxUnconfirmed(completedTxPtr: FFIPointer, confirmationCountBytes: ByteArray) { + val confirmationCount = BigInteger(1, confirmationCountBytes).toInt() + val completed = CompletedTx(completedTxPtr) + logger.i("Tx faux unconfirmed ${completed.id}") + listener.onTxMinedUnconfirmed(completed, confirmationCount) + } + + private fun onDirectSendResult(bytes: ByteArray, pointer: FFIPointer) { + val txId = BigInteger(1, bytes) + logger.i("Tx direct send result $txId") + listener.onDirectSendResult(txId, FFITransactionSendStatus(pointer).getStatus()) + } + + private fun onTxCancelled(completedTx: FFIPointer, rejectionReason: ByteArray) { + val rejectionReasonInt = BigInteger(1, rejectionReason).toInt() + val tx = FFICompletedTx(completedTx) + logger.i("Tx cancelled ${tx.getId()}") + val cancelledTx = CancelledTx(tx) + if (tx.getDirection() == Tx.Direction.OUTBOUND) { + listener.onTxCancelled(cancelledTx, rejectionReasonInt) + } + } + + private fun onBaseNodeStatus(baseNodeStatePointer: FFIPointer) { + val baseNodeState = FFITariBaseNodeState(baseNodeStatePointer) + logger.i("Base node state updated (height of the longest chain is ${baseNodeState.getHeightOfLongestChain()})") + listener.onBaseNodeStateChanged(baseNodeState) + } + + private fun onConnectivityStatus(bytes: ByteArray) { + val connectivityStatus = BigInteger(1, bytes) + logger.i("ConnectivityStatus is [$connectivityStatus]") + listener.onConnectivityStatus(connectivityStatus.toInt()) + } + + private fun onWalletScannedHeight(bytes: ByteArray) { + val height = BigInteger(1, bytes) + logger.i("Wallet scanned height is [$height]") + listener.onWalletScannedHeight(height.toInt()) + } + + private fun onBalanceUpdated(ptr: FFIPointer) { + logger.i("Balance Updated") + val balance = FFIBalance(ptr).runWithDestroy { BalanceInfo(it.getAvailable(), it.getIncoming(), it.getOutgoing(), it.getTimeLocked()) } + listener.onBalanceUpdated(balance) + } + + private fun onTXOValidationComplete(bytes: ByteArray, statusBytes: ByteArray) { + val requestId = BigInteger(1, bytes) + val statusInteger = BigInteger(1, statusBytes).toInt() + val status = TransactionValidationStatus.entries.firstOrNull { it.value == statusInteger } ?: return + logger.i("TXO validation [$requestId] complete. Result: $status") + listener.onTXOValidationComplete(requestId, status) + } + + private fun onTxValidationComplete(requestIdBytes: ByteArray, statusBytes: ByteArray) { + val requestId = BigInteger(1, requestIdBytes) + val statusInteger = BigInteger(1, statusBytes).toInt() + val status = TransactionValidationStatus.entries.firstOrNull { it.value == statusInteger } ?: return + logger.i("Tx validation [$requestId] complete. Result: $status") + listener.onTxValidationComplete(requestId, status) + } + + private fun onContactLivenessDataUpdated(livenessUpdate: FFIPointer) { + logger.i("OnContactLivenessDataUpdated") + } +} diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFIWalletListener.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFIWalletListener.kt index 3de821383..888192554 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFIWalletListener.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFIWalletListener.kt @@ -33,7 +33,7 @@ package com.tari.android.wallet.ffi import com.tari.android.wallet.model.* -import com.tari.android.wallet.model.recovery.WalletRestorationResult +import com.tari.android.wallet.recovery.WalletRestorationState import java.math.BigInteger /** @@ -56,7 +56,7 @@ interface FFIWalletListener { fun onTxValidationComplete(responseId: BigInteger, status: TransactionValidationStatus) fun onBalanceUpdated(balanceInfo: BalanceInfo) fun onConnectivityStatus(status: Int) + fun onWalletRestoration(state: WalletRestorationState) fun onWalletScannedHeight(height: Int) - fun onWalletRestoration(result: WalletRestorationResult) fun onBaseNodeStateChanged(baseNodeState: FFITariBaseNodeState) } diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt index 8006149d6..5a0303fe1 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt @@ -34,12 +34,12 @@ package com.tari.android.wallet.infrastructure.backup import com.google.gson.Gson import com.orhanobut.logger.Logger +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.backup.BackupPrefRepository import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.extension.encrypt import com.tari.android.wallet.ffi.FFIError -import com.tari.android.wallet.ffi.FFIWallet import com.tari.android.wallet.infrastructure.backup.compress.CompressionMethod import com.tari.android.wallet.infrastructure.security.encryption.SymmetricEncryptionAlgorithm import com.tari.android.wallet.model.fullBase58 @@ -57,6 +57,7 @@ class BackupFileProcessor @Inject constructor( private val securityPrefRepository: SecurityPrefRepository, private val walletConfig: WalletConfig, private val namingPolicy: BackupNamingPolicy, + private val walletManager: WalletManager, ) { private val logger get() = Logger.t(BackupFileProcessor::class.simpleName) @@ -77,7 +78,7 @@ class BackupFileProcessor @Inject constructor( if (backupPassword.isNullOrEmpty()) { val mimeType = "application/json" - val ffiWallet = FFIWallet.instance!! + val ffiWallet = walletManager.walletInstance ?: error("Wallet is not initialized during backup") val json = Gson().toJson( BackupUtxos( @@ -157,4 +158,3 @@ class BackupFileProcessor @Inject constructor( } } } - diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupManager.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupManager.kt index b5e19a12b..2149449ee 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupManager.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupManager.kt @@ -37,7 +37,11 @@ import android.content.Intent import androidx.fragment.app.Fragment import com.orhanobut.logger.Logger import com.tari.android.wallet.R +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.application.walletManager.WalletManager.WalletEvent +import com.tari.android.wallet.data.sharedPrefs.backup.BackupPrefRepository import com.tari.android.wallet.data.sharedPrefs.delegates.SerializableTime +import com.tari.android.wallet.di.ApplicationScope import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.infrastructure.backup.dropbox.DropboxBackupStorage @@ -45,11 +49,9 @@ import com.tari.android.wallet.infrastructure.backup.googleDrive.GoogleDriveBack import com.tari.android.wallet.infrastructure.backup.local.LocalBackupStorage import com.tari.android.wallet.notification.NotificationHelper import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptionDto -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions -import com.tari.android.wallet.data.sharedPrefs.backup.BackupPrefRepository +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -66,22 +68,21 @@ class BackupManager @Inject constructor( private val localFileBackupStorage: LocalBackupStorage, private val googleDriveBackupStorage: GoogleDriveBackupStorage, private val dropboxBackupStorage: DropboxBackupStorage, - private val notificationHelper: NotificationHelper + private val notificationHelper: NotificationHelper, + private val walletManager: WalletManager, + @ApplicationScope private val applicationScope: CoroutineScope, ) { private val logger get() = Logger.t(BackupManager::class.simpleName) - var currentOption: BackupOptions? = BackupOptions.Dropbox - - private val coroutineContext = Job() - private var localScope = CoroutineScope(coroutineContext) + var currentOption: BackupOption? = BackupOption.Dropbox private val backupMutex = Mutex() private val trigger = BehaviorSubject.create() - private val debouncedJob = trigger.debounce(300L, TimeUnit.MILLISECONDS) - .doOnEach { localScope.launch { backupAll() } } + private val debouncedJob = trigger.debounce(300L, TimeUnit.MILLISECONDS) // TODO don't use rx for debounce + .doOnEach { applicationScope.launch { backupAll() } } .subscribe() init { @@ -90,9 +91,29 @@ class BackupManager @Inject constructor( EventBus.subscribe(this) { trigger.onNext(Unit) } EventBus.subscribe(this) { trigger.onNext(Unit) } + + applicationScope.launch { + walletManager.walletEvent.collect { event -> + when (event) { + is WalletEvent.Tx.TxReceived, + is WalletEvent.Tx.TxReplyReceived, + is WalletEvent.Tx.TxFinalized, + is WalletEvent.Tx.InboundTxBroadcast, + is WalletEvent.Tx.OutboundTxBroadcast, + is WalletEvent.Tx.TxMined, + is WalletEvent.Tx.TxMinedUnconfirmed, + is WalletEvent.Tx.TxFauxConfirmed, + is WalletEvent.Tx.TxFauxMinedUnconfirmed, + is WalletEvent.Tx.DirectSendResult, + is WalletEvent.Tx.TxCancelled -> trigger.onNext(Unit) + + is WalletEvent.OnWalletRemove -> turnOffAll() + } + } + } } - fun setupStorage(option: BackupOptions, hostFragment: Fragment) { + fun setupStorage(option: BackupOption, hostFragment: Fragment) { currentOption = option getStorageByOption(option).setup(hostFragment) } @@ -104,7 +125,7 @@ class BackupManager @Inject constructor( private suspend fun backupAll() = backupSettingsRepository.getOptionList.forEach { backup(it.type) } - private suspend fun backup(optionType: BackupOptions) = backupMutex.withLock { + private suspend fun backup(optionType: BackupOption) = backupMutex.withLock { val currentDto = backupSettingsRepository.getOptionList.firstOrNull { it.type == optionType } ?: return if (!currentDto.isEnable) { logger.d("Backup is disabled. Exit.") @@ -148,11 +169,11 @@ class BackupManager @Inject constructor( } } - fun turnOffAll() = localScope.launch { + fun turnOffAll() = applicationScope.launch { backupSettingsRepository.getOptionList.forEach { turnOff(it.type) } } - fun turnOff(optionType: BackupOptions) = with(backupMutex) { + fun turnOff(optionType: BackupOption) = with(backupMutex) { val backupsState = EventBus.backupState.publishSubject.value!!.copy() backupSettingsRepository.updateOption(BackupOptionDto(optionType)) backupSettingsRepository.backupPassword = null @@ -160,7 +181,7 @@ class BackupManager @Inject constructor( backupsState.copy(backupsStates = backupsState.backupsStates.toMutableMap().also { it[optionType] = BackupState.BackupDisabled }) EventBus.backupState.post(newState) val backupStorage = getStorageByOption(optionType) - localScope.launch { backupStorage.signOut() } + applicationScope.launch { backupStorage.signOut() } } suspend fun signOut() { @@ -177,10 +198,10 @@ class BackupManager @Inject constructor( else -> BackupState.BackupUpToDate } - private fun getStorageByOption(optionType: BackupOptions): BackupStorage = when (optionType) { - BackupOptions.Google -> googleDriveBackupStorage - BackupOptions.Local -> localFileBackupStorage - BackupOptions.Dropbox -> dropboxBackupStorage + private fun getStorageByOption(optionType: BackupOption): BackupStorage = when (optionType) { + BackupOption.Google -> googleDriveBackupStorage + BackupOption.Local -> localFileBackupStorage + BackupOption.Dropbox -> dropboxBackupStorage } private fun postBackupFailedNotification(exception: Exception) { diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupUtxos.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupUtxos.kt index 7b440f64b..091440c1f 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupUtxos.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupUtxos.kt @@ -2,4 +2,4 @@ package com.tari.android.wallet.infrastructure.backup import com.tari.android.wallet.ffi.Base58 -data class BackupUtxos(val utxos: List?, val sourceBase58: Base58) \ No newline at end of file +data class BackupUtxos(val utxos: List, val sourceBase58: Base58) \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupsState.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupsState.kt index 96e4d6886..8bb33d52d 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupsState.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupsState.kt @@ -1,8 +1,8 @@ package com.tari.android.wallet.infrastructure.backup -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption -data class BackupsState(val backupsStates: Map) { +data class BackupsState(val backupsStates: Map) { val backupsState: BackupState get() { diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothClient.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothClient.kt index 05898237f..09f538c5a 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothClient.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothClient.kt @@ -9,7 +9,7 @@ import android.os.Looper import android.os.ParcelUuid import androidx.lifecycle.viewModelScope import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.welie.blessed.BluetoothCentralManager import com.welie.blessed.BluetoothCentralManagerCallback import com.welie.blessed.BluetoothPeripheral @@ -24,7 +24,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class TariBluetoothClient @Inject constructor(val deeplinkHandler: DeeplinkHandler) : TariBluetoothAdapter() { +class TariBluetoothClient @Inject constructor(val deeplinkManager: DeeplinkManager) : TariBluetoothAdapter() { var onSuccessSharing: () -> Unit = {} var onFailedSharing: (String) -> Unit = {} @@ -34,9 +34,11 @@ class TariBluetoothClient @Inject constructor(val deeplinkHandler: DeeplinkHandl var foundDevice: BluetoothPeripheral? = null var myGatt: BluetoothGatt? = null - val manager: BluetoothCentralManager by lazy { BluetoothCentralManager(fragappCompatActivity!!, callback, Handler(Looper.getMainLooper())).apply { - disableLogging() - } } + val manager: BluetoothCentralManager by lazy { + BluetoothCentralManager(fragappCompatActivity!!, callback, Handler(Looper.getMainLooper())).apply { + disableLogging() + } + } val callback = object : BluetoothCentralManagerCallback() { @@ -137,7 +139,7 @@ class TariBluetoothClient @Inject constructor(val deeplinkHandler: DeeplinkHandl private fun doHandling(string: String): GattStatus { logger.i("contactlessPayment: handle: url: $string") - val handled = runCatching { deeplinkHandler.handle(string) }.getOrNull() + val handled = runCatching { deeplinkManager.parseDeepLink(string) }.getOrNull() logger.i("contactlessPayment: handle: handled: $handled") diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt index 7a23e07c1..9a5bdbcc1 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt @@ -8,9 +8,9 @@ import android.bluetooth.le.AdvertiseData import android.bluetooth.le.AdvertiseSettings import android.os.ParcelUuid import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.data.sharedPrefs.bluetooth.BluetoothServerState +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.data.sharedPrefs.bluetooth.BluetoothPrefRepository +import com.tari.android.wallet.data.sharedPrefs.bluetooth.BluetoothServerState import com.tari.android.wallet.extension.addTo import com.tari.android.wallet.util.ContactUtil import com.welie.blessed.BluetoothCentral @@ -29,7 +29,7 @@ import javax.inject.Singleton @Singleton class TariBluetoothServer @Inject constructor( private val shareSettingsRepository: BluetoothPrefRepository, - private val deeplinkHandler: DeeplinkHandler, + private val deeplinkManager: DeeplinkManager, private val contactUtil: ContactUtil, ) : TariBluetoothAdapter() { @@ -120,7 +120,7 @@ class TariBluetoothServer @Inject constructor( private fun doHandling(string: String): GattStatus { logger.i("share: handle: url: $string") - val handled = runCatching { deeplinkHandler.handle(string) }.getOrNull() + val handled = runCatching { deeplinkManager.parseDeepLink(string) }.getOrNull() logger.i("share: handle: handled: $handled") @@ -141,7 +141,7 @@ class TariBluetoothServer @Inject constructor( fun initiateReading() { if (shareChunkedData.isNotEmpty()) return val myWalletAddress = sharedPrefsRepository.walletAddress - val data = deeplinkHandler.getDeeplink( + val data = deeplinkManager.getDeeplinkString( DeepLink.UserProfile( tariAddress = sharedPrefsRepository.walletAddressBase58.orEmpty(), alias = contactUtil.normalizeAlias( diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/BugReportingService.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/BugReportingService.kt index 694fb1eec..dd6e1ba51 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/BugReportingService.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/BugReportingService.kt @@ -34,7 +34,7 @@ package com.tari.android.wallet.infrastructure.logging import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import io.sentry.Attachment import io.sentry.Sentry import io.sentry.UserFeedback @@ -72,7 +72,7 @@ class BugReportingService @Inject constructor(private val sharedPrefsWrapper: Co } val fileOutStream = FileOutputStream(zipFile) // zip! - val allLogFiles = WalletUtil.getLogFilesFromDirectory(logFilesDirPath) + val allLogFiles = WalletFileUtil.getLogFilesFromDirectory(logFilesDirPath) ZipOutputStream(BufferedOutputStream(fileOutStream)).use { out -> for (file in allLogFiles) { FileInputStream(file).use { inputStream -> diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/FFIFileAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/FFIFileAdapter.kt index 0edf6ec83..0dc024aaf 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/FFIFileAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/FFIFileAdapter.kt @@ -7,7 +7,7 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter -class FFIFileAdapter : LogAdapter { +class FFIFileAdapter(private val wallet: FFIWallet?) : LogAdapter { override fun isLoggable(priority: Int, tag: String?): Boolean = true @@ -25,7 +25,7 @@ class FFIFileAdapter : LogAdapter { } val dateTimeNow = dateTimeFormatter.format(LocalDateTime.now()) val debugLine = "$dateTimeNow [${tag ?: ""}] $priorityName ${message.replace("\n", " ")}" - FFIWallet.instance?.logMessage(debugLine) + wallet?.logMessage(debugLine) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt index debadf79c..84f1bf293 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt @@ -5,6 +5,7 @@ import com.orhanobut.logger.FormatStrategy import com.orhanobut.logger.Logger import com.orhanobut.logger.PrettyFormatStrategy import com.tari.android.wallet.BuildConfig +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import javax.inject.Inject @@ -12,7 +13,11 @@ import javax.inject.Singleton @Singleton -class LoggerAdapter @Inject constructor(val walletConfig: WalletConfig, private val sentryPrefRepository: SentryPrefRepository) { +class LoggerAdapter @Inject constructor( + private val walletConfig: WalletConfig, + private val walletManager: WalletManager, + private val sentryPrefRepository: SentryPrefRepository, +) { fun init() { val formatStrategy: FormatStrategy = PrettyFormatStrategy.newBuilder() .showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true @@ -22,7 +27,7 @@ class LoggerAdapter @Inject constructor(val walletConfig: WalletConfig, private .build() Logger.addLogAdapter(AndroidLogAdapter(formatStrategy)) - Logger.addLogAdapter(FFIFileAdapter()) + Logger.addLogAdapter(FFIFileAdapter(walletManager.walletInstance)) @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR != "privacy") { Logger.addLogAdapter(SentryLogAdapter(walletConfig, sentryPrefRepository)) diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt index 5aae4d368..fd18eeab3 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt @@ -4,7 +4,7 @@ import com.orhanobut.logger.LogAdapter import com.orhanobut.logger.Logger import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.welie.blessed.BluetoothPeripheralManager import io.sentry.Attachment import io.sentry.Breadcrumb @@ -30,7 +30,7 @@ class SentryLogAdapter(val walletConfig: WalletConfig, if (priority == Logger.ERROR) { localScope.launch(Dispatchers.IO) { try { - val files = WalletUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() + val files = WalletFileUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() val lines = files.firstOrNull()?.inputStream()?.bufferedReader()?.readLines()?.takeLast(100)?.joinToString("\n") val breadcrumb = Breadcrumb(message).apply { diff --git a/app/src/main/java/com/tari/android/wallet/model/TariContact.kt b/app/src/main/java/com/tari/android/wallet/model/TariContact.kt index 18ab06cac..f9e9bacc7 100644 --- a/app/src/main/java/com/tari/android/wallet/model/TariContact.kt +++ b/app/src/main/java/com/tari/android/wallet/model/TariContact.kt @@ -48,15 +48,11 @@ data class TariContact( val isFavorite: Boolean = false, ) : Parcelable { - // TODO create a constructor that takes an wallet address only and use normalized alias - constructor(ffiContact: FFIContact) : this( walletAddress = TariWalletAddress(ffiContact.getWalletAddress()), alias = ffiContact.getAlias(), isFavorite = ffiContact.getIsFavorite() ) - fun filtered(text: String): Boolean = walletAddress.fullEmojiId.contains(text, ignoreCase = true) || alias.contains(text, ignoreCase = true) - override fun toString() = "Contact(alias='$alias') ${super.toString()}" } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/model/recovery/WalletRestorationResult.kt b/app/src/main/java/com/tari/android/wallet/model/recovery/WalletRestorationResult.kt deleted file mode 100644 index 5cbb24b6d..000000000 --- a/app/src/main/java/com/tari/android/wallet/model/recovery/WalletRestorationResult.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.tari.android.wallet.model.recovery - -import com.orhanobut.logger.Logger -import java.nio.ByteBuffer - -// If connection to a base node is successful the flow of callbacks should be: -// - The process will start with a callback with `ConnectingToBaseNode` showing a connection is being attempted -// this could be repeated multiple times until a connection is made. -// - The next a callback with `ConnectedToBaseNode` indicate a successful base node connection and process has -// started -// - In Progress callbacks will be of the form (n, m) where n < m -// - If the process completed successfully then the final `Completed` callback will return how many UTXO's were -// scanned and how much MicroTari was recovered -// - If there is an error in the connection process then the `ConnectionToBaseNodeFailed` will be returned -// - If there is a minor error in scanning then `ScanningRoundFailed` will be returned and another connection/sync -// attempt will be made -// - If a unrecoverable error occurs the `RecoveryFailed` event will be returned and the client will need to start -// a new process. -sealed class WalletRestorationResult { - class ConnectingToBaseNode : WalletRestorationResult() - class ConnectedToBaseNode : WalletRestorationResult() - class ConnectionToBaseNodeFailed(val retryCount: Long, val retryLimit: Long) : WalletRestorationResult() { - override fun toString(): String = "${this.javaClass.simpleName} $retryCount / $retryLimit" - } - class Progress(val currentBlock: Long, val numberOfBlocks: Long) : WalletRestorationResult() - class Completed(val numberOfUTXO: Long, val microTari: ByteArray) : WalletRestorationResult() - class ScanningRoundFailed(val retryCount: Long, val retryLimit: Long) : WalletRestorationResult() { - override fun toString(): String = "${this.javaClass.simpleName} $retryCount / $retryLimit" - } - class RecoveryFailed : WalletRestorationResult() - - companion object { - fun create(event: Int, firstArg: ByteArray, secondArgs: ByteArray) : WalletRestorationResult { - val first = bytesToLong(firstArg) - val second = bytesToLong(secondArgs) - Logger.t("WalletRestorationResult $event $first $second") - return when(event) { - 0 -> ConnectingToBaseNode() - 1 -> ConnectedToBaseNode() - 2 -> ConnectionToBaseNodeFailed(first, second) - 3 -> Progress(first, second) - 4 -> Completed(first, secondArgs) - 5 -> ScanningRoundFailed(first, second) - 6 -> RecoveryFailed() - else -> TODO() - } - } - - private fun bytesToLong(bytes: ByteArray): Long = ByteBuffer.allocate(java.lang.Long.BYTES).apply { - put(bytes) - flip() - }.long - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/model/seedPhrase/SeedPhrase.kt b/app/src/main/java/com/tari/android/wallet/model/seedPhrase/SeedPhrase.kt index c375459b6..d3e133857 100644 --- a/app/src/main/java/com/tari/android/wallet/model/seedPhrase/SeedPhrase.kt +++ b/app/src/main/java/com/tari/android/wallet/model/seedPhrase/SeedPhrase.kt @@ -1,44 +1,53 @@ package com.tari.android.wallet.model.seedPhrase +import com.orhanobut.logger.Logger import com.tari.android.wallet.ffi.FFISeedWords class SeedPhrase { - var ffiSeedWords: FFISeedWords? = null - private set + sealed class SeedPhraseCreationResult { + data class Success(val ffiSeedWords: FFISeedWords) : SeedPhraseCreationResult() + data class Failed(val exception: Throwable) : SeedPhraseCreationResult() + data object InvalidSeedPhrase : SeedPhraseCreationResult() + data object SeedPhraseNotCompleted : SeedPhraseCreationResult() + data object InvalidSeedWord : SeedPhraseCreationResult() + } + + companion object { + private val logger + get() = Logger.t(SeedPhrase::class.simpleName) + + const val SEED_PHRASE_LENGTH: Int = 24 - fun init(words: List): SeedPhraseCreationResult { - val ffiSeedWords = FFISeedWords() + fun create(words: List): SeedPhraseCreationResult { + val ffiSeedWords = FFISeedWords() - try { - for (seedWord in words) { - return when (ffiSeedWords.pushWord(seedWord)) { - SeedWordsWordPushResult.InvalidSeedWord -> SeedPhraseCreationResult.InvalidSeedWord - SeedWordsWordPushResult.SuccessfulPush -> continue - SeedWordsWordPushResult.SeedPhraseComplete -> { - this.ffiSeedWords = ffiSeedWords - SeedPhraseCreationResult.Success + try { + for (seedWord in words) { + return when (ffiSeedWords.pushWord(seedWord)) { + SeedWordsWordPushResult.InvalidSeedWord -> SeedPhraseCreationResult.InvalidSeedWord + SeedWordsWordPushResult.SuccessfulPush -> continue + SeedWordsWordPushResult.SeedPhraseComplete -> SeedPhraseCreationResult.Success(ffiSeedWords) + SeedWordsWordPushResult.InvalidSeedPhrase -> SeedPhraseCreationResult.InvalidSeedPhrase } - SeedWordsWordPushResult.InvalidSeedPhrase -> SeedPhraseCreationResult.InvalidSeedPhrase } + } catch (e: Throwable) { + return SeedPhraseCreationResult.Failed(e) } - } catch (e: Throwable) { - return SeedPhraseCreationResult.Failed(e) - } - - return SeedPhraseCreationResult.SeedPhraseNotCompleted - } - - sealed class SeedPhraseCreationResult { - object Success : SeedPhraseCreationResult() - class Failed(val exception: Throwable) : SeedPhraseCreationResult() - object InvalidSeedPhrase : SeedPhraseCreationResult() - object SeedPhraseNotCompleted : SeedPhraseCreationResult() - object InvalidSeedWord : SeedPhraseCreationResult() - } + return SeedPhraseCreationResult.SeedPhraseNotCompleted + } - companion object { - const val SeedPhraseLength: Int = 24 + fun createOrNull(words: List?): FFISeedWords? { + return words?.let { + when (val result = create(words)) { + is SeedPhraseCreationResult.Success -> result.ffiSeedWords + else -> { + logger.i("Seed phrase creation failed: $result") + null + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateHandler.kt b/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateHandler.kt new file mode 100644 index 000000000..ab979ca65 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateHandler.kt @@ -0,0 +1,21 @@ +package com.tari.android.wallet.network + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NetworkConnectionStateHandler @Inject constructor() { + + private val _networkConnectionState = MutableStateFlow(NetworkConnectionState.UNKNOWN) + val networkConnectionState = _networkConnectionState.asStateFlow() + + fun updateState(state: NetworkConnectionState) { + _networkConnectionState.value = state + } + + fun isNetworkConnected(): Boolean { + return networkConnectionState.value == NetworkConnectionState.CONNECTED + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateReceiver.kt b/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateReceiver.kt index 323c11d35..0f880d56a 100644 --- a/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateReceiver.kt +++ b/app/src/main/java/com/tari/android/wallet/network/NetworkConnectionStateReceiver.kt @@ -39,15 +39,14 @@ import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET -import android.os.Build -import androidx.annotation.RequiresApi import com.orhanobut.logger.Logger -import com.tari.android.wallet.event.EventBus import javax.inject.Inject import javax.inject.Singleton @Singleton -class NetworkConnectionStateReceiver @Inject constructor() : BroadcastReceiver() { +class NetworkConnectionStateReceiver @Inject constructor( + private val networkConnectionStateHandler: NetworkConnectionStateHandler, +) : BroadcastReceiver() { private val action = "android.net.conn.CONNECTIVITY_CHANGE" val intentFilter = IntentFilter(action) @@ -55,7 +54,7 @@ class NetworkConnectionStateReceiver @Inject constructor() : BroadcastReceiver() get() = Logger.t(NetworkConnectionStateReceiver::class.simpleName) init { - EventBus.networkConnectionState.post(NetworkConnectionState.UNKNOWN) + networkConnectionStateHandler.updateState(NetworkConnectionState.UNKNOWN) } override fun onReceive(context: Context?, intent: Intent?) { @@ -65,29 +64,14 @@ class NetworkConnectionStateReceiver @Inject constructor() : BroadcastReceiver() val mContext = context ?: return if (isInternetAvailable(mContext)) { logger.i("Connected to the internet") - EventBus.networkConnectionState.post(NetworkConnectionState.CONNECTED) + networkConnectionStateHandler.updateState(NetworkConnectionState.CONNECTED) } else { logger.i("Disconnected from the internet") - EventBus.networkConnectionState.post(NetworkConnectionState.DISCONNECTED) + networkConnectionStateHandler.updateState(NetworkConnectionState.DISCONNECTED) } } - private fun isInternetAvailable(context: Context): Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - isConnectedNewApi(context) - } else { - isConnectedOld(context) - } - - @Suppress("DEPRECATION") - fun isConnectedOld(context: Context): Boolean { - val connManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - val networkInfo = connManager.activeNetworkInfo - return networkInfo?.isConnected == true - - } - - @RequiresApi(Build.VERSION_CODES.M) - fun isConnectedNewApi(context: Context): Boolean { + private fun isInternetAvailable(context: Context): Boolean { val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) return capabilities?.hasCapability(NET_CAPABILITY_INTERNET) == true diff --git a/app/src/main/java/com/tari/android/wallet/notification/CustomTxNotificationViewHolder.kt b/app/src/main/java/com/tari/android/wallet/notification/CustomTxNotificationViewHolder.kt index 1eb193c4c..d05d84750 100644 --- a/app/src/main/java/com/tari/android/wallet/notification/CustomTxNotificationViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/notification/CustomTxNotificationViewHolder.kt @@ -40,7 +40,7 @@ import com.tari.android.wallet.R import com.tari.android.wallet.model.MicroTari import com.tari.android.wallet.model.TariContact import com.tari.android.wallet.model.Tx -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.util.addressFirstEmojis import com.tari.android.wallet.util.addressLastEmojis import com.tari.android.wallet.util.addressPrefixEmojis @@ -92,7 +92,7 @@ class CustomTxNotificationViewHolder(val context: Context, tx: Tx) : RemoteViews if (deviceIsLocked) { setTextViewText(R.id.notification_tx_received_txt_positive_amount, context.getString(R.string.common_new_uppercase)) } else { - val formattedValue = "+" + WalletUtil.amountFormatter.format(amount.tariValue) + val formattedValue = "+" + WalletFileUtil.amountFormatter.format(amount.tariValue) setTextViewText(R.id.notification_tx_received_txt_positive_amount, formattedValue) } setViewVisibility(R.id.notification_tx_received_txt_positive_amount, View.VISIBLE) @@ -103,7 +103,7 @@ class CustomTxNotificationViewHolder(val context: Context, tx: Tx) : RemoteViews if (deviceIsLocked) { setTextViewText(R.id.notification_tx_received_txt_negative_amount, context.getString(R.string.common_new_uppercase)) } else { - val formattedValue = "-" + WalletUtil.amountFormatter.format(amount.tariValue) + val formattedValue = "-" + WalletFileUtil.amountFormatter.format(amount.tariValue) setTextViewText(R.id.notification_tx_received_txt_negative_amount, formattedValue) } setViewVisibility(R.id.notification_tx_received_txt_negative_amount, View.VISIBLE) diff --git a/app/src/main/java/com/tari/android/wallet/notification/NotificationBroadcastReceiver.kt b/app/src/main/java/com/tari/android/wallet/notification/NotificationBroadcastReceiver.kt index 6d5f7cac8..80d10904b 100644 --- a/app/src/main/java/com/tari/android/wallet/notification/NotificationBroadcastReceiver.kt +++ b/app/src/main/java/com/tari/android/wallet/notification/NotificationBroadcastReceiver.kt @@ -36,21 +36,30 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.orhanobut.logger.Logger -import com.tari.android.wallet.event.EventBus -import com.tari.android.wallet.model.recovery.WalletRestorationResult -import com.tari.android.wallet.ui.fragment.splash.SplashActivity +import com.tari.android.wallet.di.DiContainer +import com.tari.android.wallet.recovery.WalletRestorationState +import com.tari.android.wallet.recovery.WalletRestorationStateHandler import com.tari.android.wallet.ui.fragment.home.HomeActivity import com.tari.android.wallet.ui.fragment.restore.activity.WalletRestoreActivity +import com.tari.android.wallet.ui.fragment.splash.SplashActivity +import javax.inject.Inject class NotificationBroadcastReceiver : BroadcastReceiver() { + @Inject + lateinit var walletRestorationStateHandler: WalletRestorationStateHandler + private val logger get() = Logger.t(NotificationBroadcastReceiver::class.simpleName) + init { + DiContainer.appComponent.inject(this) + } + override fun onReceive(context: Context, intent: Intent) { logger.d("NotificationBroadcastReceiver received") - val restorationState = EventBus.walletRestorationState.publishSubject.value - val newIntent: Intent = if (restorationState != null && restorationState !is WalletRestorationResult.Completed) { + val restorationState = walletRestorationStateHandler.walletRestorationState.value + val newIntent: Intent = if (restorationState !is WalletRestorationState.Completed) { Intent(context, WalletRestoreActivity::class.java) } else { if (HomeActivity.instance.get() != null) { diff --git a/app/src/main/java/com/tari/android/wallet/notification/NotificationHelper.kt b/app/src/main/java/com/tari/android/wallet/notification/NotificationHelper.kt index 95c8db7c4..7d97f5d1c 100644 --- a/app/src/main/java/com/tari/android/wallet/notification/NotificationHelper.kt +++ b/app/src/main/java/com/tari/android/wallet/notification/NotificationHelper.kt @@ -50,7 +50,7 @@ import com.tari.android.wallet.model.CancelledTx import com.tari.android.wallet.model.Tx import com.tari.android.wallet.model.TxId import com.tari.android.wallet.ui.fragment.home.HomeDeeplinkScreens -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import javax.inject.Inject import javax.inject.Singleton @@ -132,7 +132,7 @@ class NotificationHelper @Inject constructor(private val context: Context) { val notificationTitle = context.getString(R.string.notification_tx_received_title) // format spannable string val formattedAmount = if (tx.amount.tariValue.toDouble() % 1 == 0.toDouble()) tx.amount.tariValue.toBigInteger().toString() - else WalletUtil.amountFormatter.format(tx.amount.tariValue) + else WalletFileUtil.amountFormatter.format(tx.amount.tariValue) val notificationBody = context.getString(R.string.notification_tx_received_description_format, formattedAmount) val layout = CustomTxNotificationViewHolder(context, tx) val intent = Intent(context, NotificationBroadcastReceiver::class.java).apply { diff --git a/app/src/main/java/com/tari/android/wallet/notification/TxCanceledViewHolder.kt b/app/src/main/java/com/tari/android/wallet/notification/TxCanceledViewHolder.kt index 68efb0421..edea586b1 100644 --- a/app/src/main/java/com/tari/android/wallet/notification/TxCanceledViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/notification/TxCanceledViewHolder.kt @@ -37,7 +37,7 @@ import android.content.Context import android.widget.RemoteViews import com.tari.android.wallet.R import com.tari.android.wallet.model.CancelledTx -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil class TxCanceledViewHolder(context: Context, tx: CancelledTx) : RemoteViews(context.packageName, R.layout.notification_remote_tx_canceled) { @@ -48,7 +48,7 @@ class TxCanceledViewHolder(context: Context, tx: CancelledTx) : val deviceIsLocked = keyguardManager?.isDeviceLocked ?: true val amount = if (deviceIsLocked) context.getString(R.string.common_new_uppercase) - else WalletUtil.amountFormatter.format(tx.amount.tariValue) + else WalletFileUtil.amountFormatter.format(tx.amount.tariValue) setTextViewText(R.id.notification_tx_canceled_amount_text_view, amount) } diff --git a/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationState.kt b/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationState.kt new file mode 100644 index 000000000..d02aed3a3 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationState.kt @@ -0,0 +1,51 @@ +package com.tari.android.wallet.recovery + +import com.orhanobut.logger.Logger +import java.nio.ByteBuffer + +/** + * Represents the various states of the wallet restoration process. + * + * The flow of callbacks for a successful connection to a base node is as follows: + * + * - **ConnectingToBaseNode**: The process starts with a callback indicating that a connection attempt is in progress. + * This callback may be repeated multiple times until a connection is established. + * - **ConnectedToBaseNode**: Once a connection is successfully made, this callback is triggered, indicating that the process has started. + * - **Progress**: Progress callbacks will be in the form of (n, m) where n < m, showing the progress of the process. + * - **Completed**: If the process completes successfully, this callback will be returned, indicating the number of UTXOs scanned and the amount of MicroTari recovered. + * - **ConnectionToBaseNodeFailed**: If there is an error during the connection process, this callback will be returned. + * - **ScanningRoundFailed**: If there is a minor error during scanning, this callback will be returned, and another connection/sync attempt will be made. + * - **RecoveryFailed**: If an unrecoverable error occurs, this event will be returned, and the client will need to start a new process. + */ +sealed class WalletRestorationState { + data object ConnectingToBaseNode : WalletRestorationState() + data object ConnectedToBaseNode : WalletRestorationState() + data class ConnectionToBaseNodeFailed(val retryCount: Long, val retryLimit: Long) : WalletRestorationState() + data class Progress(val currentBlock: Long, val numberOfBlocks: Long) : WalletRestorationState() + data class Completed(val numberOfUTXO: Long, val microTari: ByteArray) : WalletRestorationState() + data class ScanningRoundFailed(val retryCount: Long, val retryLimit: Long) : WalletRestorationState() + data object RecoveryFailed : WalletRestorationState() + + companion object { + fun create(event: Int, firstArg: ByteArray, secondArgs: ByteArray): WalletRestorationState { + val first = bytesToLong(firstArg) + val second = bytesToLong(secondArgs) + Logger.t("WalletRestorationResult $event $first $second") + return when (event) { + 0 -> ConnectingToBaseNode + 1 -> ConnectedToBaseNode + 2 -> ConnectionToBaseNodeFailed(first, second) + 3 -> Progress(first, second) + 4 -> Completed(first, secondArgs) + 5 -> ScanningRoundFailed(first, second) + 6 -> RecoveryFailed + else -> error("Invalid event type: $event") + } + } + + private fun bytesToLong(bytes: ByteArray): Long = ByteBuffer.allocate(java.lang.Long.BYTES).apply { + put(bytes) + flip() + }.long + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationStateHandler.kt b/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationStateHandler.kt new file mode 100644 index 000000000..151cab568 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/recovery/WalletRestorationStateHandler.kt @@ -0,0 +1,18 @@ +package com.tari.android.wallet.recovery + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WalletRestorationStateHandler @Inject constructor() { + + private val _walletRestorationState = MutableStateFlow(WalletRestorationState.ConnectingToBaseNode) + val walletRestorationState = _walletRestorationState.asStateFlow() + + fun updateState(newState: WalletRestorationState) { + _walletRestorationState.update { newState } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/BootDeviceReceiver.kt b/app/src/main/java/com/tari/android/wallet/service/BootDeviceReceiver.kt index 861b722b1..5a1624c7d 100644 --- a/app/src/main/java/com/tari/android/wallet/service/BootDeviceReceiver.kt +++ b/app/src/main/java/com/tari/android/wallet/service/BootDeviceReceiver.kt @@ -63,7 +63,7 @@ class BootDeviceReceiver : BroadcastReceiver() { val sharedPreferences = context.getSharedPreferences(ApplicationModule.sharedPrefsFileName, Context.MODE_PRIVATE) val networkRepository = NetworkPrefRepositoryImpl(sharedPreferences) val tariSettingsSharedRepository = TariSettingsPrefRepository(sharedPreferences, networkRepository) - WalletServiceLauncher(context, WalletConfig(context, networkRepository), tariSettingsSharedRepository).startIfExist() + WalletServiceLauncher(context, WalletConfig(context, networkRepository), tariSettingsSharedRepository).startIfWalletExists() } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/ServiceRestartBroadcastReceiver.kt b/app/src/main/java/com/tari/android/wallet/service/ServiceRestartBroadcastReceiver.kt index e9aa062bb..32caf51fc 100644 --- a/app/src/main/java/com/tari/android/wallet/service/ServiceRestartBroadcastReceiver.kt +++ b/app/src/main/java/com/tari/android/wallet/service/ServiceRestartBroadcastReceiver.kt @@ -58,6 +58,6 @@ class ServiceRestartBroadcastReceiver : BroadcastReceiver() { val sharedPreferences = context.getSharedPreferences(ApplicationModule.sharedPrefsFileName, Context.MODE_PRIVATE) val networkRepository = NetworkPrefRepositoryImpl(sharedPreferences) val tariSettingsSharedRepository = TariSettingsPrefRepository(sharedPreferences, networkRepository) - WalletServiceLauncher(context, WalletConfig(context, networkRepository), tariSettingsSharedRepository).startIfExist() + WalletServiceLauncher(context, WalletConfig(context, networkRepository), tariSettingsSharedRepository).startIfWalletExists() } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/baseNode/BaseNodeStateHandler.kt b/app/src/main/java/com/tari/android/wallet/service/baseNode/BaseNodeStateHandler.kt new file mode 100644 index 000000000..d73495e7c --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/service/baseNode/BaseNodeStateHandler.kt @@ -0,0 +1,31 @@ +package com.tari.android.wallet.service.baseNode + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BaseNodeStateHandler @Inject constructor() { + + /** + * Base node state. Showing the wallet is connected to the base node or not. + */ + private val _baseNodeState = MutableStateFlow(BaseNodeState.Syncing) + val baseNodeState = _baseNodeState.asStateFlow() + + /** + * Base node sync state. Showing the wallet validation status after connecting to the base node. + */ + private val _baseNodeSyncState = MutableStateFlow(BaseNodeSyncState.NotStarted) + val baseNodeSyncState = _baseNodeSyncState.asStateFlow() + + fun updateState(state: BaseNodeState) { + _baseNodeState.update { state } + } + + fun updateSyncState(state: BaseNodeSyncState) { + _baseNodeSyncState.update { state } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/seedPhrase/SeedPhraseRepository.kt b/app/src/main/java/com/tari/android/wallet/service/seedPhrase/SeedPhraseRepository.kt deleted file mode 100644 index fb22354e6..000000000 --- a/app/src/main/java/com/tari/android/wallet/service/seedPhrase/SeedPhraseRepository.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tari.android.wallet.service.seedPhrase - -import com.tari.android.wallet.model.seedPhrase.SeedPhrase -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SeedPhraseRepository @Inject constructor() { - - private var seedPhrase: SeedPhrase? = null - - fun save(seedPhrase: SeedPhrase) { - this.seedPhrase = seedPhrase - } - - fun getPhrase(): SeedPhrase? = seedPhrase -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/service/BaseNodeValidationType.kt b/app/src/main/java/com/tari/android/wallet/service/service/BaseNodeValidationType.kt deleted file mode 100644 index e8fba37e9..000000000 --- a/app/src/main/java/com/tari/android/wallet/service/service/BaseNodeValidationType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tari.android.wallet.service.service - -enum class BaseNodeValidationType { - TXO, - TX; -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/service/FFIWalletListenerImpl.kt b/app/src/main/java/com/tari/android/wallet/service/service/FFIWalletListenerImpl.kt deleted file mode 100644 index bf857471d..000000000 --- a/app/src/main/java/com/tari/android/wallet/service/service/FFIWalletListenerImpl.kt +++ /dev/null @@ -1,343 +0,0 @@ -package com.tari.android.wallet.service.service - -import com.orhanobut.logger.Logger -import com.tari.android.wallet.application.TariWalletApplication -import com.tari.android.wallet.application.baseNodes.BaseNodesManager -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodePrefRepository -import com.tari.android.wallet.event.Event -import com.tari.android.wallet.event.EventBus -import com.tari.android.wallet.ffi.FFITariBaseNodeState -import com.tari.android.wallet.ffi.FFIWallet -import com.tari.android.wallet.ffi.FFIWalletListener -import com.tari.android.wallet.ffi.TransactionValidationStatus -import com.tari.android.wallet.infrastructure.backup.BackupManager -import com.tari.android.wallet.model.BalanceInfo -import com.tari.android.wallet.model.CancelledTx -import com.tari.android.wallet.model.CompletedTx -import com.tari.android.wallet.model.PendingInboundTx -import com.tari.android.wallet.model.PendingOutboundTx -import com.tari.android.wallet.model.TariContact -import com.tari.android.wallet.model.TariWalletAddress -import com.tari.android.wallet.model.TransactionSendStatus -import com.tari.android.wallet.model.Tx -import com.tari.android.wallet.model.TxId -import com.tari.android.wallet.model.recovery.WalletRestorationResult -import com.tari.android.wallet.notification.NotificationHelper -import com.tari.android.wallet.service.TariWalletServiceListener -import com.tari.android.wallet.service.baseNode.BaseNodeState -import com.tari.android.wallet.service.baseNode.BaseNodeSyncState -import com.tari.android.wallet.service.notification.NotificationService -import com.tari.android.wallet.ui.fragment.home.HomeActivity -import io.reactivex.Observable -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import java.math.BigInteger -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap -import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.CopyOnWriteArraySet -import java.util.concurrent.TimeUnit - -class FFIWalletListenerImpl( - private val wallet: FFIWallet, - private val backupManager: BackupManager, - private val notificationHelper: NotificationHelper, - private val notificationService: NotificationService, - private val app: TariWalletApplication, - private val baseNodeSharedPrefsRepository: BaseNodePrefRepository, - private val baseNodesManager: BaseNodesManager -) : FFIWalletListener { - - private val logger - get() = Logger.t("FFIWalletListenerImpl") - var listeners = CopyOnWriteArrayList() - - /** - * Maps the validation type to the request id and validation result. This map will be - * initialized at the beginning of each base node validation sequence. - * Validation results will all be null, and will be set as the result callbacks get called. - */ - var baseNodeValidationStatusMap: ConcurrentMap> = ConcurrentHashMap() - - /** - * Debounce for inbound transaction notification. - */ - private var txReceivedNotificationDelayedAction: Disposable? = null - private var inboundTxEventNotificationTxs = mutableListOf() - - private var txBroadcastRestarted = false - - val outboundTxIdsToBePushNotified = CopyOnWriteArraySet() - - override fun onTxReceived(pendingInboundTx: PendingInboundTx) { - val newTx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxReceived(newTx)) - // manage notifications - postTxNotification(newTx) - listeners.forEach { it.onTxReceived(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxReplyReceived(pendingOutboundTx: PendingOutboundTx) { - val newTx = pendingOutboundTx.copy(tariContact = getUserByWalletAddress(pendingOutboundTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxReplyReceived(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxReplyReceived(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxFinalized(pendingInboundTx: PendingInboundTx) { - val newTx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxFinalized(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxFinalized(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onInboundTxBroadcast(pendingInboundTx: PendingInboundTx) { - val newTx = pendingInboundTx.copy(tariContact = getUserByWalletAddress(pendingInboundTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.InboundTxBroadcast(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onInboundTxBroadcast(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onOutboundTxBroadcast(pendingOutboundTx: PendingOutboundTx) { - val newTx = pendingOutboundTx.copy(tariContact = getUserByWalletAddress(pendingOutboundTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.OutboundTxBroadcast(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onOutboundTxBroadcast(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxMined(completedTx: CompletedTx) { - val newTx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxMined(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxMined(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxMinedUnconfirmed(completedTx: CompletedTx, confirmationCount: Int) { - val newTx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxMinedUnconfirmed(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxMinedUnconfirmed(newTx, confirmationCount) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxFauxConfirmed(completedTx: CompletedTx) { - val newTx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxFauxConfirmed(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxFauxConfirmed(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTxFauxUnconfirmed(completedTx: CompletedTx, confirmationCount: Int) { - val newTx = completedTx.copy(tariContact = getUserByWalletAddress(completedTx.tariContact.walletAddress)) - // post event to bus for the listeners - EventBus.post(Event.Transaction.TxFauxMinedUnconfirmed(newTx)) - // notify external listeners - listeners.iterator().forEach { it.onTxFauxUnconfirmed(newTx, confirmationCount) } - // schedule a backup - backupManager.backupNow() - } - - override fun onDirectSendResult(txId: BigInteger, status: TransactionSendStatus) { - // post event to bus - EventBus.post(Event.Transaction.DirectSendResult(TxId(txId), status)) - outboundTxIdsToBePushNotified.firstOrNull { it.txId == txId }?.let { - outboundTxIdsToBePushNotified.remove(it) - sendPushNotificationToTxRecipient(it.recipientPublicKeyHex) - } - // schedule a backup - backupManager.backupNow() - // notify external listeners - listeners.iterator().forEach { it.onDirectSendResult(TxId(txId), status) } - } - - override fun onTxCancelled(cancelledTx: CancelledTx, rejectionReason: Int) { - val newTx = cancelledTx.copy(tariContact = getUserByWalletAddress(cancelledTx.tariContact.walletAddress)) - // post event to bus - EventBus.post(Event.Transaction.TxCancelled(newTx)) - val currentActivity = app.currentActivity - if (cancelledTx.direction == Tx.Direction.INBOUND && !(app.isInForeground && currentActivity is HomeActivity && currentActivity.willNotifyAboutNewTx()) - ) { - notificationHelper.postTxCanceledNotification(newTx) - } - // notify external listeners - listeners.iterator().forEach { listener -> listener.onTxCancelled(newTx) } - // schedule a backup - backupManager.backupNow() - } - - override fun onTXOValidationComplete(responseId: BigInteger, status: TransactionValidationStatus) { - checkValidationResult(BaseNodeValidationType.TXO, responseId, status == TransactionValidationStatus.Success) - } - - override fun onTxValidationComplete(responseId: BigInteger, status: TransactionValidationStatus) { - checkValidationResult(BaseNodeValidationType.TX, responseId, status == TransactionValidationStatus.Success) - if (!txBroadcastRestarted && status == TransactionValidationStatus.Success) { - wallet.restartTxBroadcast() - txBroadcastRestarted = true - } - } - - override fun onBalanceUpdated(balanceInfo: BalanceInfo) { - EventBus.balanceState.post(balanceInfo) - // notify external listeners - listeners.iterator().forEach { it.onBalanceUpdated(balanceInfo) } - } - - override fun onConnectivityStatus(status: Int) { - when (ConnectivityStatus.entries[status]) { - ConnectivityStatus.CONNECTING -> { - /* do nothing */ - } - - ConnectivityStatus.ONLINE -> { - baseNodesManager.refreshBaseNodeList() - baseNodeSharedPrefsRepository.baseNodeState = BaseNodeState.Online - EventBus.baseNodeState.post(BaseNodeState.Online) - listeners.iterator().forEach { it.onBaseNodeSyncComplete(true) } - } - - ConnectivityStatus.OFFLINE -> { - val currentBaseNode = baseNodeSharedPrefsRepository.currentBaseNode - if (currentBaseNode == null || !currentBaseNode.isCustom) { - baseNodesManager.setNextBaseNode() - baseNodesManager.startSync() - } - baseNodeSharedPrefsRepository.baseNodeState = BaseNodeState.Offline - EventBus.baseNodeState.post(BaseNodeState.Offline) - listeners.iterator().forEach { it.onBaseNodeSyncComplete(false) } - } - - } - } - - private fun getUserByWalletAddress(address: TariWalletAddress): TariContact { - val contactsFFI = wallet.getContacts() - for (i in 0 until contactsFFI.getLength()) { - val contactFFI = contactsFFI.getAt(i) - val walletAddressFFI = contactFFI.getWalletAddress() - val tariContact = if (TariWalletAddress(walletAddressFFI) == address) TariContact(contactFFI) else null - walletAddressFFI.destroy() - contactFFI.destroy() - if (tariContact != null) { - contactsFFI.destroy() - return tariContact - } - } - // destroy native collection - contactsFFI.destroy() - return TariContact(address) - } - - fun postTxNotification(tx: Tx) { - txReceivedNotificationDelayedAction?.dispose() - inboundTxEventNotificationTxs.add(tx) - txReceivedNotificationDelayedAction = - Observable.timer(500, TimeUnit.MILLISECONDS) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe { - // if app is backgrounded, display heads-up notification - val currentActivity = app.currentActivity - if (!app.isInForeground - || currentActivity !is HomeActivity - || !currentActivity.willNotifyAboutNewTx() - ) { - notificationHelper.postCustomLayoutTxNotification(inboundTxEventNotificationTxs.last()) - } - inboundTxEventNotificationTxs.clear() - } - } - - private fun sendPushNotificationToTxRecipient(recipientHex: String) { - val senderHex = wallet.getWalletAddress().notificationHex() - notificationService.notifyRecipient(recipientHex, senderHex, wallet::signMessage) - } - - private fun checkBaseNodeSyncCompletion() { - // make a copy of the status map for concurrency protection - val statusMapCopy = baseNodeValidationStatusMap.toMap() - // if base node not in sync, then switch to the next base node - // check if any has failed - val failed = statusMapCopy.any { it.value.second == false } - if (failed) { - baseNodeValidationStatusMap.clear() - baseNodeSharedPrefsRepository.baseNodeLastSyncResult = false - val currentBaseNode = baseNodeSharedPrefsRepository.currentBaseNode - if (currentBaseNode == null || !currentBaseNode.isCustom) { - baseNodesManager.setNextBaseNode() - baseNodesManager.startSync() - } - EventBus.baseNodeSyncState.post(BaseNodeSyncState.Failed) - listeners.iterator().forEach { it.onBaseNodeSyncComplete(false) } - return - } - // if any of the results is null, we're still waiting for all callbacks to happen - val inProgress = statusMapCopy.any { it.value.second == null } - if (inProgress) { - return - } - // check if it's successful - val successful = statusMapCopy.all { it.value.second == true } - if (successful) { - baseNodeValidationStatusMap.clear() - baseNodeSharedPrefsRepository.baseNodeLastSyncResult = true - EventBus.baseNodeSyncState.post(BaseNodeSyncState.Online) - listeners.iterator().forEach { it.onBaseNodeSyncComplete(true) } - } - // shouldn't ever reach here - no-op - } - - private fun checkValidationResult(type: BaseNodeValidationType, responseId: BigInteger, isSuccess: Boolean) { - try { - val currentStatus = baseNodeValidationStatusMap[type] ?: return - if (currentStatus.first != responseId) return - baseNodeValidationStatusMap[type] = Pair(currentStatus.first, isSuccess) - checkBaseNodeSyncCompletion() - } catch (e: Throwable) { - logger.i(e.toString()) - } - } - - override fun onWalletRestoration(result: WalletRestorationResult) { - EventBus.walletRestorationState.post(result) - } - - override fun onBaseNodeStateChanged(baseNodeState: FFITariBaseNodeState) { - baseNodesManager.saveBaseNodeState(baseNodeState) - } - - override fun onWalletScannedHeight(height: Int) { - baseNodesManager.saveWalletScannedHeight(height) - } - - enum class ConnectivityStatus(val value: Int) { - CONNECTING(0), - ONLINE(1), - OFFLINE(2), - } - - data class OutboundTxNotification(val txId: BigInteger, val recipientPublicKeyHex: String) -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt index 49cd6b694..7f0b0dd2d 100644 --- a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt +++ b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt @@ -1,16 +1,13 @@ package com.tari.android.wallet.service.service -import com.orhanobut.logger.Logger -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodePrefRepository -import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.application.walletManager.OutboundTxNotifier +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.ffi.Base58String import com.tari.android.wallet.ffi.FFIContact import com.tari.android.wallet.ffi.FFIError import com.tari.android.wallet.ffi.FFIException -import com.tari.android.wallet.ffi.FFIPublicKey import com.tari.android.wallet.ffi.FFITariWalletAddress import com.tari.android.wallet.ffi.FFIWallet -import com.tari.android.wallet.ffi.HexString import com.tari.android.wallet.ffi.runWithDestroy import com.tari.android.wallet.model.BalanceInfo import com.tari.android.wallet.model.CancelledTx @@ -26,23 +23,16 @@ import com.tari.android.wallet.model.TariVector import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.TxId import com.tari.android.wallet.model.WalletError -import com.tari.android.wallet.model.fullBase58 import com.tari.android.wallet.service.TariWalletService -import com.tari.android.wallet.service.TariWalletServiceListener -import com.tari.android.wallet.service.baseNode.BaseNodeSyncState import com.tari.android.wallet.util.Constants import java.math.BigInteger import java.util.Locale class TariWalletServiceStubImpl( private val wallet: FFIWallet, - private val baseNodeSharedPrefsRepository: BaseNodePrefRepository, - private val walletServiceListener: FFIWalletListenerImpl + private val outboundTxNotifier: OutboundTxNotifier, ) : TariWalletService.Stub() { - private val logger - get() = Logger.t(WalletService::class.simpleName) - private var _cachedTariContacts: List? = null private val cachedTariContacts: List @Synchronized get() { @@ -60,16 +50,6 @@ class TariWalletServiceStubImpl( return tariContacts.sortedWith(compareBy { it.alias }).also { _cachedTariContacts = it } } - override fun registerListener(listener: TariWalletServiceListener): Boolean { - walletServiceListener.listeners.add(listener) - listener.asBinder().linkToDeath({ walletServiceListener.listeners.remove(listener) }, 0) - return true - } - - override fun unregisterListener(listener: TariWalletServiceListener): Boolean = walletServiceListener.listeners.remove(listener) - - override fun getWalletAddressBase58(error: WalletError): String? = runMapping(error) { wallet.getWalletAddress().fullBase58() } - override fun getBalanceInfo(error: WalletError): BalanceInfo? = runMapping(error) { wallet.getBalance() } override fun estimateTxFee(amount: MicroTari, error: WalletError, feePerGram: MicroTari?): MicroTari? = runMapping(error) { @@ -148,30 +128,6 @@ class TariWalletServiceStubImpl( override fun cancelPendingTx(id: TxId, error: WalletError): Boolean = runMapping(error) { wallet.cancelPendingTx(id.value) } ?: false - override fun addBaseNodePeer(baseNodePublicKey: String, baseNodeAddress: String, error: WalletError): Boolean = runMapping(error) { - Logger.t(this::class.simpleName).e("walletServiceStub:addBaseNodePeer:publicKeyHex: $baseNodePublicKey") - Logger.t(this::class.simpleName).e("walletServiceStub:addBaseNodePeer:address: $baseNodeAddress") - val result = FFIPublicKey(HexString(baseNodePublicKey)).runWithDestroy { wallet.addBaseNodePeer(it, baseNodeAddress) } - if (result) { - walletServiceListener.baseNodeValidationStatusMap.clear() - EventBus.baseNodeSyncState.post(BaseNodeSyncState.NotStarted) - } - result - } ?: false - - override fun startBaseNodeSync(error: WalletError): Boolean = runMapping(error, { - logger.i(it.toString() + "Base node sync failed") - baseNodeSharedPrefsRepository.baseNodeLastSyncResult = false - walletServiceListener.baseNodeValidationStatusMap.clear() - EventBus.baseNodeSyncState.post(BaseNodeSyncState.Failed) - }) { - walletServiceListener.baseNodeValidationStatusMap.clear() - walletServiceListener.baseNodeValidationStatusMap[BaseNodeValidationType.TXO] = Pair(wallet.startTXOValidation(), null) - walletServiceListener.baseNodeValidationStatusMap[BaseNodeValidationType.TX] = Pair(wallet.startTxValidation(), null) - baseNodeSharedPrefsRepository.baseNodeLastSyncResult = null - true - } ?: false - override fun sendTari( tariContact: TariContact, amount: MicroTari, @@ -184,8 +140,8 @@ class TariWalletServiceStubImpl( val recipientAddress = FFITariWalletAddress(Base58String(tariContact.walletAddress.fullBase58)) val txId = wallet.sendTx(recipientAddress, amount.value, feePerGram.value, message, isOneSidePayment, paymentId) - walletServiceListener.outboundTxIdsToBePushNotified.add( - FFIWalletListenerImpl.OutboundTxNotification( + outboundTxNotifier.outboundTxIdsToBePushNotified.add( + WalletManager.OutboundTxNotification( txId = txId, recipientPublicKeyHex = recipientAddress.notificationHex().lowercase(Locale.ENGLISH), ) @@ -226,12 +182,6 @@ class TariWalletServiceStubImpl( } } ?: false - override fun getWalletAddressFromEmojiId(emojiId: String?, error: WalletError): TariWalletAddress? = - runMapping(error) { FFITariWalletAddress(emojiId = emojiId.orEmpty()).runWithDestroy { TariWalletAddress(it) } } - - override fun getWalletAddressFromBase58(walletAddressBase58: String?, error: WalletError): TariWalletAddress? = - runMapping(error) { FFITariWalletAddress(base58 = Base58String(walletAddressBase58 ?: "")).runWithDestroy { TariWalletAddress(it) } } - override fun setKeyValue(key: String, value: String, error: WalletError): Boolean = runMapping(error) { wallet.setKeyValue(key, value) } ?: false override fun getKeyValue(key: String, error: WalletError): String? = runMapping(error) { wallet.getKeyValue(key) } diff --git a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubProxy.kt b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubProxy.kt index e0432b8c8..783770922 100644 --- a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubProxy.kt +++ b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubProxy.kt @@ -15,7 +15,6 @@ import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.TxId import com.tari.android.wallet.model.WalletError import com.tari.android.wallet.service.TariWalletService -import com.tari.android.wallet.service.TariWalletServiceListener class TariWalletServiceStubProxy : TariWalletService.Stub() { @@ -27,12 +26,6 @@ class TariWalletServiceStubProxy : TariWalletService.Stub() { _stub = newStub } - override fun registerListener(listener: TariWalletServiceListener): Boolean = stub?.registerListener(listener) ?: false - - override fun unregisterListener(listener: TariWalletServiceListener): Boolean = stub?.unregisterListener(listener) ?: false - - override fun getWalletAddressBase58(error: WalletError): String? = stub?.getWalletAddressBase58(error) - override fun getBalanceInfo(error: WalletError): BalanceInfo? = stub?.getBalanceInfo(error) override fun estimateTxFee(amount: MicroTari, error: WalletError, feePerGram: MicroTari?): MicroTari? = @@ -58,11 +51,6 @@ class TariWalletServiceStubProxy : TariWalletService.Stub() { override fun cancelPendingTx(id: TxId, error: WalletError): Boolean = stub?.cancelPendingTx(id, error) ?: false - override fun addBaseNodePeer(baseNodePublicKey: String, baseNodeAddress: String, error: WalletError): Boolean = - stub?.addBaseNodePeer(baseNodePublicKey, baseNodeAddress, error) ?: false - - override fun startBaseNodeSync(error: WalletError): Boolean = stub?.startBaseNodeSync(error) ?: false - override fun sendTari( contact: TariContact, amount: MicroTari, @@ -79,11 +67,6 @@ class TariWalletServiceStubProxy : TariWalletService.Stub() { override fun removeContact(contactPublicKey: TariWalletAddress, error: WalletError): Boolean = stub?.removeContact(contactPublicKey, error) ?: false - override fun getWalletAddressFromBase58(base58: String, error: WalletError): TariWalletAddress? = stub?.getWalletAddressFromBase58(base58, error) - - override fun getWalletAddressFromEmojiId(emojiId: String, error: WalletError): TariWalletAddress? = - stub?.getWalletAddressFromEmojiId(emojiId, error) - override fun setKeyValue(key: String, value: String, error: WalletError): Boolean = stub?.setKeyValue(key, value, error) ?: false override fun getKeyValue(key: String, error: WalletError): String? = stub?.getKeyValue(key, error) diff --git a/app/src/main/java/com/tari/android/wallet/service/service/WalletService.kt b/app/src/main/java/com/tari/android/wallet/service/service/WalletService.kt index b74300073..b1af66148 100644 --- a/app/src/main/java/com/tari/android/wallet/service/service/WalletService.kt +++ b/app/src/main/java/com/tari/android/wallet/service/service/WalletService.kt @@ -40,25 +40,18 @@ import android.os.Looper import androidx.lifecycle.ProcessLifecycleOwner import com.orhanobut.logger.Logger import com.tari.android.wallet.application.TariWalletApplication -import com.tari.android.wallet.application.baseNodes.BaseNodesManager import com.tari.android.wallet.application.walletManager.WalletManager -import com.tari.android.wallet.application.walletManager.WalletStateHandler -import com.tari.android.wallet.data.WalletConfig -import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodePrefRepository +import com.tari.android.wallet.application.walletManager.doOnWalletStarted import com.tari.android.wallet.di.DiContainer import com.tari.android.wallet.ffi.FFIWallet import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.notification.NotificationHelper import com.tari.android.wallet.service.ServiceRestartBroadcastReceiver -import com.tari.android.wallet.service.notification.NotificationService -import com.tari.android.wallet.service.service.WalletServiceLauncher.Companion.startAction -import com.tari.android.wallet.service.service.WalletServiceLauncher.Companion.stopAction -import com.tari.android.wallet.service.service.WalletServiceLauncher.Companion.stopAndDeleteAction +import com.tari.android.wallet.service.service.WalletServiceLauncher.Companion.START_ACTION +import com.tari.android.wallet.service.service.WalletServiceLauncher.Companion.STOP_ACTION import com.tari.android.wallet.ui.common.domain.ResourceManager import com.tari.android.wallet.ui.fragment.settings.logs.LogFilesManager import com.tari.android.wallet.util.Constants -import com.tari.android.wallet.util.WalletUtil import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -79,39 +72,21 @@ import javax.inject.Inject */ class WalletService : Service() { - @Inject - lateinit var walletConfig: WalletConfig - @Inject lateinit var app: TariWalletApplication @Inject lateinit var resourceManager: ResourceManager - @Inject - lateinit var notificationService: NotificationService - @Inject lateinit var notificationHelper: NotificationHelper - @Inject - lateinit var sharedPrefsWrapper: CorePrefRepository - - @Inject - lateinit var baseNodeSharedPrefsRepository: BaseNodePrefRepository - @Inject lateinit var walletManager: WalletManager @Inject lateinit var backupManager: BackupManager - @Inject - lateinit var baseNodesManager: BaseNodesManager - - @Inject - lateinit var walletStateHandler: WalletStateHandler - private var lifecycleObserver: ServiceLifecycleCallbacks? = null private val stubProxy = TariWalletServiceStubProxy() @@ -120,7 +95,7 @@ class WalletService : Service() { private lateinit var wallet: FFIWallet private val job = SupervisorJob() - private val scope = CoroutineScope(Dispatchers.IO + job) + private val serviceScope = CoroutineScope(Dispatchers.IO + job) private val logger get() = Logger.t(WalletService::class.simpleName) @@ -146,30 +121,23 @@ class WalletService : Service() { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { startForeground() when (intent.action) { - startAction -> startService() - stopAction -> stopService(startId) - stopAndDeleteAction -> { - //todo total crutch. Service is auto-creating during the bind func. Need to refactor this first - DiContainer.appComponent.inject(this) - stopService(startId) - deleteWallet() - } - + START_ACTION -> startService(intent.getStringArrayExtra(WalletServiceLauncher.ARG_SEED_WORDS)?.toList()) + STOP_ACTION -> stopService(startId) else -> throw RuntimeException("Unexpected intent action: ${intent.action}") } return START_NOT_STICKY } - private fun startService() { + private fun startService(seedWords: List?) { //todo total crutch. Service is auto-creating during the bind func. Need to refactor this first DiContainer.appComponent.inject(this) - scope.launch { - walletStateHandler.doOnWalletStarted { + serviceScope.launch { + walletManager.doOnWalletStarted { onWalletStarted(it) } } - walletManager.start() + walletManager.start(seedWords) logger.i("Wallet service started") } @@ -187,26 +155,13 @@ class WalletService : Service() { lifecycleObserver?.let { ProcessLifecycleOwner.get().lifecycle.removeObserver(it) } } - private fun deleteWallet() { - WalletUtil.clearWalletFiles(walletConfig.getWalletFilesDirPath()) - sharedPrefsWrapper.clear() - backupManager.turnOffAll() - } - private fun onWalletStarted(ffiWallet: FFIWallet) { wallet = ffiWallet lifecycleObserver = ServiceLifecycleCallbacks(wallet) - val impl = FFIWalletListenerImpl( + stubProxy.stub = TariWalletServiceStubImpl( wallet = wallet, - backupManager = backupManager, - notificationHelper = notificationHelper, - notificationService = notificationService, - app = app, - baseNodeSharedPrefsRepository = baseNodeSharedPrefsRepository, - baseNodesManager = baseNodesManager, + outboundTxNotifier = walletManager, ) - stubProxy.stub = TariWalletServiceStubImpl(wallet, baseNodeSharedPrefsRepository, impl) - wallet.listener = impl scheduleExpirationCheck() Handler(Looper.getMainLooper()).post { ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver!!) } walletManager.onWalletStarted() @@ -298,7 +253,7 @@ class WalletService : Service() { object KeyValueStorageKeys { const val NETWORK = "SU7FM2O6Q3BU4XVN7HDD" - const val version = "version" + const val VERSION = "version" } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/service/service/WalletServiceLauncher.kt b/app/src/main/java/com/tari/android/wallet/service/service/WalletServiceLauncher.kt index 2a71087c6..50b28c6fa 100644 --- a/app/src/main/java/com/tari/android/wallet/service/service/WalletServiceLauncher.kt +++ b/app/src/main/java/com/tari/android/wallet/service/service/WalletServiceLauncher.kt @@ -4,34 +4,40 @@ import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat import com.tari.android.wallet.application.TariWalletApplication +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsPrefRepository -import com.tari.android.wallet.util.WalletUtil +/** + * Wallet service launcher. Starts the wallet service that starts the wallet. + */ class WalletServiceLauncher( private val context: Context, val walletConfig: WalletConfig, val tariSettingsSharedRepository: TariSettingsPrefRepository ) { - fun startIfExist() { - if (WalletUtil.walletExists(walletConfig)) { - startService() + fun startIfWalletExists(seedWords: List? = null) { + if (WalletFileUtil.walletExists(walletConfig)) { + startService(seedWords) } } - fun start() { - if (tariSettingsSharedRepository.backgroundServiceTurnedOn || - !tariSettingsSharedRepository.backgroundServiceTurnedOn && TariWalletApplication.INSTANCE.get()?.isInForeground == true - ) { - startService() + // We can't pass a FFISeedWords object to the service, so we pass the seed words as a list of strings + fun start(seedWords: List? = null) { + val backgroundServiceTurnedOn = tariSettingsSharedRepository.backgroundServiceTurnedOn + val appInForeground = TariWalletApplication.INSTANCE.get()?.isInForeground == true + if (backgroundServiceTurnedOn || appInForeground) { + startService(seedWords) } } - private fun startService() = ContextCompat.startForegroundService(context, getStartIntent(context)) + private fun startService(seedWords: List?) = + ContextCompat.startForegroundService(context, Intent(context, WalletService::class.java).also { + it.action = START_ACTION + it.putExtra(ARG_SEED_WORDS, seedWords?.toTypedArray()) + }) - fun stop() = ContextCompat.startForegroundService(context, getStopIntent(context)) - - fun stopAndDelete() = ContextCompat.startForegroundService(context, getStopAndDeleteIntent(context)) + fun stop() = ContextCompat.startForegroundService(context, Intent(context, WalletService::class.java).also { it.action = STOP_ACTION }) fun startOnAppForegrounded() { if (!tariSettingsSharedRepository.backgroundServiceTurnedOn) { @@ -45,18 +51,9 @@ class WalletServiceLauncher( } } - private fun getStartIntent(context: Context) = Intent(context, WalletService::class.java).also { it.action = startAction } - - private fun getStopIntent(context: Context) = Intent(context, WalletService::class.java).also { it.action = stopAction } - - private fun getStopAndDeleteIntent(context: Context) = Intent(context, WalletService::class.java).also { it.action = stopAndDeleteAction } - - companion object { - // intent actions - const val startAction = "START_SERVICE" - const val stopAction = "STOP_SERVICE" - const val stopAndDeleteAction = "STOP_SERVICE_AND_DELETE_WALLET" + const val START_ACTION = "START_SERVICE" + const val STOP_ACTION = "STOP_SERVICE" + const val ARG_SEED_WORDS = "ARG_SEED_WORDS" } - } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/CommonFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/common/CommonFragment.kt index fa0619bbc..8c8fdadc9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/CommonFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/CommonFragment.kt @@ -11,6 +11,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment @@ -112,9 +113,9 @@ abstract class CommonFragment : Fra observe(copyToClipboard) { copy(it) } - observe(modularDialog) { dialogManager.replace(ModularDialog(requireContext(), it)) } + observe(modularDialog) { dialogManager.replace(ModularDialog(requireActivity(), it)) } - observe(inputDialog) { dialogManager.replace(InputModularDialog(requireContext(), it)) } + observe(inputDialog) { dialogManager.replace(InputModularDialog(requireActivity(), it)) } observe(showToast) { TariToast(requireContext(), it) } @@ -130,7 +131,7 @@ abstract class CommonFragment : Fra observe(permissionManager.openSettings) { openSettings() } - observe(permissionManager.dialog) { dialogManager.replace(ModularDialog(requireContext(), it)) } + observe(permissionManager.dialog) { dialogManager.replace(ModularDialog(requireActivity(), it)) } } private fun openSettings() { @@ -141,10 +142,12 @@ abstract class CommonFragment : Fra } } - protected fun changeOnBackPressed(isBlocked: Boolean) { - blockingBackPressDispatcher.isEnabled = false - blockingBackPressDispatcher = MutedBackPressedCallback(isBlocked) - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, blockingBackPressDispatcher) + protected fun doOnBackPressed(onBackPressedAction: () -> Unit) { + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + onBackPressedAction() + } + }) } private fun copy(clipboardArgs: ClipboardArgs) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt index 7eeb7b9e7..917bbce85 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt @@ -4,11 +4,12 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.orhanobut.logger.Logger import com.orhanobut.logger.Printer import com.tari.android.wallet.R -import com.tari.android.wallet.application.walletManager.WalletStateHandler +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.application.walletManager.doOnWalletFailed +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository @@ -17,6 +18,7 @@ import com.tari.android.wallet.di.ApplicationComponent import com.tari.android.wallet.di.DiContainer import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.addTo +import com.tari.android.wallet.extension.launchOnIo import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.ffi.FFIWallet import com.tari.android.wallet.infrastructure.logging.LoggerTags @@ -42,9 +44,6 @@ import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.AllSetting import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator import com.tari.android.wallet.ui.fragment.settings.themeSelector.TariTheme import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import javax.inject.Inject open class CommonViewModel : ViewModel() { @@ -81,7 +80,7 @@ open class CommonViewModel : ViewModel() { get() = serviceConnection.walletService @Inject - lateinit var walletStateHandler: WalletStateHandler + lateinit var walletManager: WalletManager @Inject lateinit var dialogManager: DialogManager @@ -127,8 +126,8 @@ open class CommonViewModel : ViewModel() { checkAuthorization() }.addTo(compositeDisposable) - viewModelScope.launch { - walletStateHandler.doOnWalletFailed { + launchOnIo { + walletManager.doOnWalletFailed { showErrorDialog(it) } } @@ -143,14 +142,14 @@ open class CommonViewModel : ViewModel() { } fun doOnWalletServiceConnected(action: suspend (walletService: TariWalletService) -> Unit) { - viewModelScope.launch { + launchOnIo { serviceConnection.doOnWalletServiceConnected(action) } } fun doOnWalletRunning(action: suspend (walletService: FFIWallet) -> Unit) { - viewModelScope.launch { - walletStateHandler.doOnWalletRunning(action) + launchOnIo { + walletManager.doOnWalletRunning(action) } } @@ -176,8 +175,6 @@ open class CommonViewModel : ViewModel() { } } - fun doOnBackground(action: suspend CoroutineScope.() -> Unit): Job = viewModelScope.launch { action() } - fun showModularDialog(args: ModularDialogArgs) { _modularDialog.postValue(args) } diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/DialogManager.kt b/app/src/main/java/com/tari/android/wallet/ui/common/DialogManager.kt index 35d320858..ab17e0af1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/DialogManager.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/DialogManager.kt @@ -1,7 +1,13 @@ package com.tari.android.wallet.ui.common +import android.app.Activity +import com.tari.android.wallet.R import com.tari.android.wallet.ui.dialog.modular.ModularDialog +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs.DialogId +import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule +import com.tari.android.wallet.ui.dialog.modular.modules.imageModule.ImageModule import javax.inject.Inject import javax.inject.Singleton @@ -20,6 +26,10 @@ class DialogManager @Inject constructor() { } } + fun replace(context: Activity, args: ModularDialogArgs) { + replace(ModularDialog(context, args)) + } + /** * Dismisses the dialog with the given dialogId. If dialogId is [DialogId.NO_ID], the last dialog in the queue will be dismissed. */ @@ -33,8 +43,25 @@ class DialogManager @Inject constructor() { dialogQueue.remove(dialogToDismiss) } + fun dismissAll() { + dialogQueue.forEach { it.dismiss() } + dialogQueue.clear() + } + fun isDialogShowing(dialogId: Int) = dialogId != DialogId.NO_ID && dialogQueue.any { it.args.dialogId == dialogId } + fun showNotReadyYetDialog(context: Activity) { + replace( + context, ModularDialogArgs( + modules = listOf( + ImageModule(R.drawable.tari_construction), + HeadModule(context.getString(R.string.common_not_ready_yet_dialog_title)), + BodyModule(context.getString(R.string.common_not_ready_yet_dialog_description)), + ) + ) + ) + } + private fun getDialog(dialogId: Int): ModularDialog? = if (dialogId != DialogId.NO_ID) dialogQueue.firstOrNull { it.args.dialogId == dialogId } else null } diff --git a/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt index 9a7b4ab91..cee92a4d9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt @@ -2,7 +2,7 @@ package com.tari.android.wallet.ui.component.clipboardController import android.content.ClipboardManager import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.extension.launchOnIo import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.model.TariWalletAddress @@ -16,7 +16,7 @@ class WalletAddressViewModel : CommonViewModel() { lateinit var clipboardManager: ClipboardManager @Inject - lateinit var deeplinkHandler: DeeplinkHandler + lateinit var deeplinkManager: DeeplinkManager val discoveredWalletAddressFromClipboard = SingleLiveEvent() @@ -53,7 +53,7 @@ class WalletAddressViewModel : CommonViewModel() { } private fun findValidEmojiId(query: String): TariWalletAddress? { - return when (val deepLink = deeplinkHandler.handle(query)) { + return when (val deepLink = deeplinkManager.parseDeepLink(query)) { is DeepLink.Send -> deepLink.walletAddress is DeepLink.UserProfile -> deepLink.tariAddress is DeepLink.Contacts -> deepLink.contacts.firstOrNull()?.tariAddress diff --git a/app/src/main/java/com/tari/android/wallet/ui/component/common/CommonView.kt b/app/src/main/java/com/tari/android/wallet/ui/component/common/CommonView.kt index 69eebc76c..3423bb921 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/component/common/CommonView.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/component/common/CommonView.kt @@ -13,6 +13,7 @@ import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.common.DialogManager import com.tari.android.wallet.ui.component.tari.toast.TariToast import com.tari.android.wallet.ui.dialog.modular.ModularDialog +import contacts.ui.view.activity abstract class CommonView : LinearLayout { @@ -58,7 +59,7 @@ abstract class CommonView : LinearLayout openLink.observe(viewLifecycle) { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it))) } - modularDialog.observe(viewLifecycle) { dialogManager.replace(ModularDialog(context, it)) } + modularDialog.observe(viewLifecycle) { args -> activity?.let { activity -> dialogManager.replace(ModularDialog(activity, args)) } } showToast.observe(viewLifecycle) { TariToast(context, it) } diff --git a/app/src/main/java/com/tari/android/wallet/ui/component/networkStateIndicator/ConnectionIndicatorViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/component/networkStateIndicator/ConnectionIndicatorViewModel.kt index 36c774922..64c05f0f1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/component/networkStateIndicator/ConnectionIndicatorViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/component/networkStateIndicator/ConnectionIndicatorViewModel.kt @@ -2,11 +2,12 @@ package com.tari.android.wallet.ui.component.networkStateIndicator import com.tari.android.wallet.R import com.tari.android.wallet.application.baseNodes.BaseNodesManager -import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.collectFlow import com.tari.android.wallet.extension.combineToPair import com.tari.android.wallet.network.NetworkConnectionState +import com.tari.android.wallet.network.NetworkConnectionStateHandler import com.tari.android.wallet.service.baseNode.BaseNodeState +import com.tari.android.wallet.service.baseNode.BaseNodeStateHandler import com.tari.android.wallet.service.baseNode.BaseNodeSyncState import com.tari.android.wallet.tor.TorProxyState import com.tari.android.wallet.tor.TorProxyStateHandler @@ -29,6 +30,12 @@ class ConnectionIndicatorViewModel : CommonViewModel() { @Inject lateinit var baseNodesManager: BaseNodesManager + @Inject + lateinit var networkConnectionStateHandler: NetworkConnectionStateHandler + + @Inject + lateinit var baseNodeStateHandler: BaseNodeStateHandler + private val _state = MutableStateFlow(UiState()) val state = _state.asStateFlow() @@ -61,19 +68,18 @@ class ConnectionIndicatorViewModel : CommonViewModel() { } private fun subscribeOnEventBus() { - EventBus.networkConnectionState.subscribe(this) { networkState -> + collectFlow(networkConnectionStateHandler.networkConnectionState) { networkState -> _state.update { it.copy(networkState = networkState) } showStatesDialog(true) } - EventBus.baseNodeState.subscribe(this) { baseNodeState -> + collectFlow(baseNodeStateHandler.baseNodeState) { baseNodeState -> _state.update { it.copy(baseNodeState = baseNodeState) } showStatesDialog(true) } - EventBus.baseNodeSyncState.subscribe(this) { syncState -> + collectFlow(baseNodeStateHandler.baseNodeSyncState) { syncState -> _state.update { it.copy(baseNodeSyncState = syncState) } showStatesDialog(true) } - collectFlow(torProxyStateHandler.torProxyState) { torProxyState -> _state.update { it.copy(torProxyState = torProxyState) } showStatesDialog(true) diff --git a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/InputModularDialog.kt b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/InputModularDialog.kt index 0c2059f18..9c648f0d1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/InputModularDialog.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/InputModularDialog.kt @@ -1,6 +1,6 @@ package com.tari.android.wallet.ui.dialog.modular -import android.content.Context +import android.app.Activity import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE @@ -10,9 +10,9 @@ import com.tari.android.wallet.R import com.tari.android.wallet.ui.component.tari.background.obsolete.TariPrimaryBackgroundConstraint -class InputModularDialog(context: Context) : ModularDialog(context) { +class InputModularDialog(context: Activity) : ModularDialog(context) { - constructor(context: Context, args: ModularDialogArgs) : this(context) { + constructor(context: Activity, args: ModularDialogArgs) : this(context) { applyArgs(args) modifyDialog() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialog.kt b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialog.kt index e26bf7d5e..788dd833f 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialog.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialog.kt @@ -3,7 +3,6 @@ package com.tari.android.wallet.ui.dialog.modular import android.animation.ValueAnimator import android.app.Activity import android.app.Dialog -import android.content.Context import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.view.Gravity @@ -48,6 +47,7 @@ import com.tari.android.wallet.ui.dialog.modular.modules.shortEmoji.ShortEmojiId import com.tari.android.wallet.ui.dialog.modular.modules.shortEmoji.ShortEmojiModuleView import com.tari.android.wallet.ui.dialog.modular.modules.space.SpaceModule import com.tari.android.wallet.ui.dialog.modular.modules.space.SpaceModuleView +import com.tari.android.wallet.ui.extension.isStillAlive import com.tari.android.wallet.ui.fragment.send.addAmount.feeModule.FeeModule import com.tari.android.wallet.ui.fragment.send.addAmount.feeModule.FeeModuleView import com.tari.android.wallet.ui.fragment.send.shareQr.ShareQRCodeModuleView @@ -64,14 +64,16 @@ import com.tari.android.wallet.ui.fragment.utxos.list.module.UtxoAmountModule import com.tari.android.wallet.ui.fragment.utxos.list.module.UtxoAmountModuleView import com.tari.android.wallet.ui.fragment.utxos.list.module.UtxoSplitModule import com.tari.android.wallet.ui.fragment.utxos.list.module.UtxoSplitModuleView +import java.lang.ref.WeakReference -open class ModularDialog(val context: Context) { +open class ModularDialog(context: Activity) { + private val weakContext = WeakReference(context) lateinit var args: ModularDialogArgs private val onDismissListeners = mutableListOf<() -> Unit>() - constructor(context: Context, args: ModularDialogArgs) : this(context) { + constructor(context: Activity, args: ModularDialogArgs) : this(context) { applyArgs(args) } @@ -85,14 +87,16 @@ open class ModularDialog(val context: Context) { } fun show() { - dialog.show() - showAnimation(true) + withContext { + dialog.show() + showAnimation(true) + } } fun dismiss() { - showAnimation(false) { - runCatching { - if (context !is Activity || !context.isFinishing) { + withContext { + showAnimation(false) { + runCatching { dialog.dismiss() } } @@ -104,24 +108,26 @@ open class ModularDialog(val context: Context) { } fun applyArgs(args: ModularDialogArgs) { - this.args = args - with(dialog) { - setCancelable(args.dialogArgs.cancelable) - setCanceledOnTouchOutside(args.dialogArgs.canceledOnTouchOutside) - setOnDismissListener { - onDismissListeners.forEach { runCatching { it() } } - args.dialogArgs.onDismiss() - } - if (args.dialogArgs.canceledOnTouchOutside) { - findViewById(R.id.back).setOnClickListener { - this@ModularDialog.dismiss() + withContext { context -> + this.args = args + with(dialog) { + setCancelable(args.dialogArgs.cancelable) + setCanceledOnTouchOutside(args.dialogArgs.canceledOnTouchOutside) + setOnDismissListener { + onDismissListeners.forEach { runCatching { it() } } + args.dialogArgs.onDismiss() + } + if (args.dialogArgs.canceledOnTouchOutside) { + findViewById(R.id.back).setOnClickListener { + this@ModularDialog.dismiss() + } } } + updateModules(context, args.modules) } - updateModules(args.modules) } - private fun updateModules(modules: List) { + private fun updateModules(context: Activity, modules: List) { val root = dialog.findViewById(R.id.dialog_root_view) root.removeAllViews() for (module in modules) { @@ -180,4 +186,10 @@ open class ModularDialog(val context: Context) { start() } } + + private fun withContext(block: (Activity) -> Unit) { + weakContext.get()?.takeIf { it.isStillAlive() }?.let { + block(it) + } + } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialogArgs.kt b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialogArgs.kt index 1585905f6..b6b656c3c 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialogArgs.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/dialog/modular/ModularDialogArgs.kt @@ -13,5 +13,10 @@ data class ModularDialogArgs( const val CONNECTION_STATUS = 601 const val DEBUG_MENU = 602 const val SCREEN_RECORDING = 603 + const val DEEPLINK_ADD_BASE_NODE = 604 + const val DEEPLINK_ADD_CONTACTS = 605 + const val DEEPLINK_PAPER_WALLET = 606 + const val DEEPLINK_PAPER_WALLET_REMEMBER_TO_BACKUP = 606 + const val DEEPLINK_PAPER_WALLET_ENTER_PASSPHRASE = 607 } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/extension/ContextExtensions.kt b/app/src/main/java/com/tari/android/wallet/ui/extension/ContextExtensions.kt index 239cbc0fe..bfaff855a 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/extension/ContextExtensions.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/extension/ContextExtensions.kt @@ -32,6 +32,7 @@ */ package com.tari.android.wallet.ui.extension +import android.app.Activity import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable @@ -67,3 +68,5 @@ fun Context.getResourceUri(resourceId: Int): Uri = Uri.Builder() .authority(packageName) .path(resourceId.toString()) .build() + +fun Context.isStillAlive(): Boolean = this is Activity && !this.isFinishing && !this.isDestroyed \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/extension/ViewExtensions.kt b/app/src/main/java/com/tari/android/wallet/ui/extension/ViewExtensions.kt index e1b5c2511..ea3183971 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/extension/ViewExtensions.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/extension/ViewExtensions.kt @@ -34,6 +34,7 @@ package com.tari.android.wallet.ui.extension import android.animation.AnimatorSet import android.animation.ValueAnimator +import android.app.Activity import android.content.Context import android.content.res.TypedArray import android.graphics.drawable.Drawable @@ -89,7 +90,7 @@ fun View.setVisible(visible: Boolean, hideState: Int = View.GONE) { /** * Given the context, displays the standard "no internet connection" dialog. */ -fun showInternetConnectionErrorDialog(context: Context) { +fun showInternetConnectionErrorDialog(context: Activity) { val args = ModularDialogArgs( DialogArgs(), listOf( HeadModule(context.string(R.string.internet_connection_error_dialog_title)), diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt index 11fdc6817..4f1f08b79 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt @@ -76,10 +76,6 @@ class AuthActivity : CommonActivity() { setupUi() - observe(viewModel.goAuth) { - viewModel.walletServiceLauncher.start() - } - doAuth() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt index 1fd27398e..404d77d32 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt @@ -7,7 +7,6 @@ import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationService import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle @@ -26,8 +25,6 @@ class AuthViewModel : CommonViewModel() { @Inject lateinit var migrationManager: MigrationManager - val goAuth = SingleLiveEvent() - init { component.inject(this) @@ -35,7 +32,7 @@ class AuthViewModel : CommonViewModel() { migrationManager.validateVersion( onValid = { launchOnMain { - goAuth.postValue(Unit) + walletServiceLauncher.start() } }, onError = { @@ -56,17 +53,16 @@ class AuthViewModel : CommonViewModel() { deleteWallet() }, ButtonModule(resourceManager.getString(R.string.ffi_validation_error_cancel), ButtonStyle.Close) { - goAuth.postValue(Unit) + walletServiceLauncher.start() hideDialog() } ) } private fun deleteWallet() { - // disable CTAs launchOnIo { - walletServiceLauncher.stopAndDelete() - navigation.postValue(Navigation.TxListNavigation.ToSplashScreen) + walletManager.deleteWallet() + tariNavigator.navigate(Navigation.SplashScreen()) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt index f9f86d22d..6100452c8 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt @@ -73,10 +73,6 @@ class FeatureAuthFragment : CommonFragment(QrScannerActivity.EXTRA_DEEPLINK) ?: return + viewModel.handleDeeplink(qrDeepLink) } } @@ -340,10 +338,8 @@ open class ContactSelectionFragment : CommonFragment Boolean = { true } val selectedContact = MutableLiveData() @@ -112,8 +109,7 @@ class ContactSelectionViewModel : CommonViewModel() { contactList.addSource(isContactlessPayment) { updateContactList() } } - fun handleDeeplink(deeplinkString: String) { - val deeplink = deeplinkFormatter.parse(deeplinkString) + fun handleDeeplink(deeplink: DeepLink) { val deeplinkBase58 = when (deeplink) { is DeepLink.Contacts -> deeplink.contacts.firstOrNull()?.tariAddress is DeepLink.Send -> deeplink.walletAddress @@ -135,7 +131,7 @@ class ContactSelectionViewModel : CommonViewModel() { deeplinkBase58?.let { TariWalletAddress.fromBase58OrNull(it) }?.let { walletAddress -> selectedContact.value = ContactDto(FFIContactInfo(walletAddress), uuid = name) _yatState.update { it.copy(yatUser = null) } - } ?: run { logger.e("Wallet address not found for deeplink: $deeplinkString") } + } ?: run { logger.e("Wallet address not found for deeplink: $deeplink") } } fun onContactlessPaymentClick() { @@ -212,6 +208,12 @@ class ContactSelectionViewModel : CommonViewModel() { } } + fun parseDeeplink(context: Activity, deeplinkString: String) { + val deeplink = deeplinkManager.parseDeepLink(deeplinkString)!! + deeplinkManager.execute(context, deeplink) + deselectTariWalletAddress() + } + private fun showCantAddYourselfDialog() { showSimpleDialog( title = resourceManager.getString(R.string.contact_book_add_contact_cant_add_yourself_title), diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/ContactsRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/ContactsRepository.kt index ed90e5e6b..d4432ac60 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/ContactsRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/ContactsRepository.kt @@ -2,7 +2,7 @@ package com.tari.android.wallet.ui.fragment.contactBook.data import android.content.Context import com.orhanobut.logger.Logger -import com.tari.android.wallet.application.walletManager.WalletStateHandler +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.di.ApplicationScope import com.tari.android.wallet.extension.replaceItem import com.tari.android.wallet.model.TariWalletAddress @@ -29,7 +29,7 @@ class ContactsRepository @Inject constructor( context: Context, contactUtil: ContactUtil, tariWalletServiceConnection: TariWalletServiceConnection, - walletStateHandler: WalletStateHandler, + walletManager: WalletManager, @ApplicationScope private val applicationScope: CoroutineScope, ) { private val logger @@ -38,7 +38,7 @@ class ContactsRepository @Inject constructor( private val ffiBridge = FFIContactsRepositoryBridge( contactsRepository = this, tariWalletServiceConnection = tariWalletServiceConnection, - walletStateHandler = walletStateHandler, + walletManager = walletManager, contactUtil = contactUtil, externalScope = applicationScope, ) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/FFIContactsRepositoryBridge.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/FFIContactsRepositoryBridge.kt index 4aa227905..b1ccbfc88 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/FFIContactsRepositoryBridge.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/data/FFIContactsRepositoryBridge.kt @@ -1,9 +1,10 @@ package com.tari.android.wallet.ui.fragment.contactBook.data import com.orhanobut.logger.Logger -import com.tari.android.wallet.application.walletManager.WalletStateHandler -import com.tari.android.wallet.event.Event -import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.application.walletManager.WalletManager.WalletEvent +import com.tari.android.wallet.application.walletManager.doOnWalletRunning +import com.tari.android.wallet.application.walletManager.doOnWalletRunningWithValue import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.WalletError import com.tari.android.wallet.service.connection.TariWalletServiceConnection @@ -17,7 +18,7 @@ import kotlinx.coroutines.launch class FFIContactsRepositoryBridge( private val contactsRepository: ContactsRepository, private val tariWalletServiceConnection: TariWalletServiceConnection, - private val walletStateHandler: WalletStateHandler, + private val walletManager: WalletManager, private val contactUtil: ContactUtil, private val externalScope: CoroutineScope, ) { @@ -26,19 +27,22 @@ class FFIContactsRepositoryBridge( init { externalScope.launch { - walletStateHandler.doOnWalletRunning { - tariWalletServiceConnection.doOnWalletServiceConnected { - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } - EventBus.subscribe(this) { contactsRepository.onNewTxReceived() } + walletManager.walletEvent.collect { event -> + when (event) { + is WalletEvent.Tx.TxReceived, + is WalletEvent.Tx.TxReplyReceived, + is WalletEvent.Tx.TxFinalized, + is WalletEvent.Tx.InboundTxBroadcast, + is WalletEvent.Tx.OutboundTxBroadcast, + is WalletEvent.Tx.TxMinedUnconfirmed, + is WalletEvent.Tx.TxMined, + is WalletEvent.Tx.TxFauxMinedUnconfirmed, + is WalletEvent.Tx.TxFauxConfirmed, + is WalletEvent.Tx.TxCancelled -> contactsRepository.onNewTxReceived() + + else -> Unit } + } } } @@ -46,7 +50,7 @@ class FFIContactsRepositoryBridge( suspend fun updateToFFI(contacts: List) { logger.i("Contacts repository event: Saving updates to FFI") - walletStateHandler.doOnWalletRunning { + walletManager.doOnWalletRunning { tariWalletServiceConnection.doOnWalletServiceConnected { service -> contacts.mapNotNull { it.getFFIContactInfo() }.forEach { ffiContactInfo -> val error = WalletError() @@ -89,7 +93,7 @@ class FFIContactsRepositoryBridge( private suspend fun loadFFIContacts(): List { val walletContacts: List = try { - walletStateHandler.doOnWalletRunningWithValue { walletService -> + walletManager.doOnWalletRunningWithValue { walletService -> walletService.getContacts().items() .map { ffiContact -> FFIContactInfo( @@ -142,7 +146,7 @@ class FFIContactsRepositoryBridge( suspend fun deleteContact(contact: FFIContactInfo) { logger.i("Contacts repository event: Deleting contact from FFI") - walletStateHandler.doOnWalletRunning { + walletManager.doOnWalletRunning { tariWalletServiceConnection.doOnWalletServiceConnected { service -> val error = WalletError() service.removeContact(contact.walletAddress, error) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ContactBookFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ContactBookFragment.kt index 49e20a8d0..9a343b417 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ContactBookFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ContactBookFragment.kt @@ -15,7 +15,7 @@ import androidx.fragment.app.viewModels import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayoutMediator import com.tari.android.wallet.R -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel +import com.tari.android.wallet.application.deeplinks.DeepLink import com.tari.android.wallet.databinding.FragmentContactBookRootBinding import com.tari.android.wallet.extension.observe import com.tari.android.wallet.model.TariWalletAddress @@ -23,6 +23,7 @@ import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.component.clipboardController.ClipboardController import com.tari.android.wallet.ui.component.tari.toolbar.TariToolbarActionArg import com.tari.android.wallet.ui.extension.hideKeyboard +import com.tari.android.wallet.ui.extension.parcelable import com.tari.android.wallet.ui.extension.postDelayed import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.extension.showKeyboard @@ -34,7 +35,7 @@ import com.tari.android.wallet.ui.fragment.contactBook.root.share.ShareOptionArg import com.tari.android.wallet.ui.fragment.contactBook.root.share.ShareOptionView import com.tari.android.wallet.ui.fragment.home.HomeActivity import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerActivity import com.tari.android.wallet.ui.fragment.qr.QrScannerSource import com.tari.android.wallet.util.Constants import java.lang.ref.WeakReference @@ -46,14 +47,11 @@ class ContactBookFragment : CommonFragment(QrScannerActivity.EXTRA_DEEPLINK) ?: return + viewModel.handleDeeplink(qrDeeplink) } } @@ -153,7 +151,7 @@ class ContactBookFragment : CommonFragment deeplink.contacts.firstOrNull()?.tariAddress is DeepLink.Send -> deeplink.walletAddress is DeepLink.UserProfile -> deeplink.tariAddress @@ -127,7 +123,7 @@ class ContactBookViewModel : CommonViewModel() { tariAddress = it.contactInfo.requireWalletAddress().fullBase58, ) } - return deeplinkHandler.getDeeplink(DeepLink.Contacts(contacts)) + return deeplinkManager.getDeeplinkString(DeepLink.Contacts(contacts)) } private fun setSelectedToShareType(shareType: ShareType) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ShareViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ShareViewModel.kt index 05a983460..8e1bed4ca 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ShareViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contactBook/root/ShareViewModel.kt @@ -3,8 +3,7 @@ package com.tari.android.wallet.ui.fragment.contactBook.root import androidx.lifecycle.MutableLiveData import com.tari.android.wallet.R import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.infrastructure.bluetooth.TariBluetoothClient import com.tari.android.wallet.infrastructure.bluetooth.TariBluetoothServer import com.tari.android.wallet.model.TariWalletAddress @@ -21,6 +20,7 @@ import com.tari.android.wallet.ui.fragment.contactBook.data.ContactsRepository import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.ContactDto import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.FFIContactInfo import com.tari.android.wallet.ui.fragment.contactBook.root.share.ShareType +import com.tari.android.wallet.ui.fragment.home.HomeActivity import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.send.shareQr.ShareQrCodeModule import com.tari.android.wallet.util.ContactUtil @@ -35,16 +35,14 @@ class ShareViewModel : CommonViewModel() { @Inject lateinit var tariBluetoothServer: TariBluetoothServer - @Inject - lateinit var deeplinkHandler: DeeplinkHandler - @Inject lateinit var contactsRepository: ContactsRepository @Inject lateinit var contactUtil: ContactUtil - val deeplinkViewModel = DeeplinkViewModel() + @Inject + lateinit var deeplinkManager: DeeplinkManager val shareText = SingleLiveEvent() @@ -160,7 +158,9 @@ class ShareViewModel : CommonViewModel() { } private fun onReceived(data: List) { - deeplinkViewModel.addContacts(DeepLink.Contacts(data)) + HomeActivity.instance.get()?.let { context -> + deeplinkManager.execute(context, DeepLink.Contacts(data)) + } } companion object { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt index 35e5c48b7..1c35744df 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt @@ -45,8 +45,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.viewpager2.adapter.FragmentStateAdapter import com.tari.android.wallet.R -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsPrefRepository @@ -91,7 +90,7 @@ class HomeActivity : CommonActivity() { lateinit var networkRepository: NetworkPrefRepository @Inject - lateinit var deeplinkHandler: DeeplinkHandler + lateinit var deeplinkManager: DeeplinkManager @Inject lateinit var resourceManager: ResourceManager @@ -99,8 +98,6 @@ class HomeActivity : CommonActivity() { @Inject lateinit var tariSettingsRepository: TariSettingsPrefRepository - val deeplinkViewModel: DeeplinkViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appComponent.inject(this) @@ -122,12 +119,10 @@ class HomeActivity : CommonActivity() { val viewModel: HomeViewModel by viewModels() bindViewModel(viewModel) - subscribeToCommon(deeplinkViewModel) subscribeToCommon(viewModel.shareViewModel) subscribeToCommon(viewModel.shareViewModel.tariBluetoothServer) subscribeToCommon(viewModel.shareViewModel.tariBluetoothClient) - subscribeToCommon(viewModel.shareViewModel.deeplinkViewModel) viewModel.shareViewModel.tariBluetoothServer.init(this) viewModel.shareViewModel.tariBluetoothClient.init(this) @@ -284,7 +279,11 @@ class HomeActivity : CommonActivity() { fun willNotifyAboutNewTx(): Boolean = ui.viewPager.currentItem == INDEX_HOME private fun processIntentDeepLink(intent: Intent) { - deeplinkViewModel.tryToHandle(intent.data?.toString().orEmpty(), false) + intent.data?.toString()?.takeIf { it.isNotEmpty() } + ?.let { deeplinkString -> deeplinkManager.parseDeepLink(deeplinkString) } + ?.let { deeplink -> + deeplinkManager.execute(context = this, deeplink = deeplink) + } } override fun onDestroy() { @@ -331,7 +330,7 @@ class HomeActivity : CommonActivity() { private const val KEY_PAGE = "key_page" @Volatile - var instance: WeakReference = WeakReference(null) + var instance: WeakReference = WeakReference(null) // TODO I don't like this. Better not to share context globally private set } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt index 6fb80252b..e34c831a0 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt @@ -1,6 +1,5 @@ package com.tari.android.wallet.ui.fragment.home -import com.tari.android.wallet.application.YatAdapter import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.fragment.contactBook.data.ContactsRepository import com.tari.android.wallet.ui.fragment.contactBook.root.ShareViewModel @@ -8,9 +7,6 @@ import javax.inject.Inject class HomeViewModel : CommonViewModel() { - @Inject - lateinit var yatAdapter: YatAdapter - @Inject lateinit var contactsRepository: ContactsRepository diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt index 1fb738144..77e780762 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt @@ -16,6 +16,7 @@ sealed class Navigation { class EnterPinCodeNavigation(val behavior: PinCodeScreenBehavior, val stashedPin: String? = null) : Navigation() object ChangeBiometrics : Navigation() object FeatureAuth : Navigation() + data class SplashScreen(val seedWords: List? = null, val clearTop: Boolean = true) : Navigation() sealed class CustomBridgeNavigation : Navigation() { object UploadQrCode : CustomBridgeNavigation() @@ -38,7 +39,6 @@ sealed class Navigation { } sealed class TxListNavigation : Navigation() { - object ToSplashScreen : TxListNavigation() class ToTxDetails(val tx: Tx) : TxListNavigation() object ToChat : TxListNavigation() object ToAllSettings : TxListNavigation() @@ -65,11 +65,6 @@ sealed class Navigation { data class OnSendTxSuccess(val isYat: Boolean, val txId: TxId) : SendTxNavigation() } - sealed class WalletRestoringFromSeedWordsNavigation : Navigation() { - object OnRestoreCompleted : WalletRestoringFromSeedWordsNavigation() - object OnRestoreFailed : WalletRestoringFromSeedWordsNavigation() - } - sealed class AllSettingsNavigation : Navigation() { object ToMyProfile : AllSettingsNavigation() object ToBugReporting : AllSettingsNavigation() @@ -88,18 +83,13 @@ sealed class Navigation { } sealed class InputSeedWordsNavigation : Navigation() { - object ToRestoreFormSeedWordsInProgress : InputSeedWordsNavigation() + object ToRestoreFromSeeds : InputSeedWordsNavigation() object ToBaseNodeSelection : InputSeedWordsNavigation() } - sealed class EnterRestorationPasswordNavigation : Navigation() { - object OnRestore : EnterRestorationPasswordNavigation() - } - sealed class ChooseRestoreOptionNavigation : Navigation() { object ToEnterRestorePassword : ChooseRestoreOptionNavigation() - object ToRestoreWithRecoveryPhrase : ChooseRestoreOptionNavigation() - object OnRestoreCompleted : ChooseRestoreOptionNavigation() + data object ToRestoreWithRecoveryPhrase : ChooseRestoreOptionNavigation() } sealed class ContactBookNavigation : Navigation() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt index 84a9fd70f..a34791748 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt @@ -8,16 +8,14 @@ import com.tari.android.wallet.R import com.tari.android.wallet.application.YatAdapter import com.tari.android.wallet.application.YatAdapter.ConnectedWallet import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository -import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsPrefRepository +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus -import com.tari.android.wallet.ffi.FFIWallet import com.tari.android.wallet.model.MicroTari import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.Tx import com.tari.android.wallet.model.TxId -import com.tari.android.wallet.network.NetworkConnectionState +import com.tari.android.wallet.network.NetworkConnectionStateHandler import com.tari.android.wallet.ui.common.CommonActivity import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.dialog.modular.DialogArgs @@ -48,20 +46,18 @@ import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.ChatNaviga import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.ChooseRestoreOptionNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.ContactBookNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.CustomBridgeNavigation -import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.EnterRestorationPasswordNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.InputSeedWordsNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.SendTxNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.TorBridgeNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.TxListNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.VerifySeedPhraseNavigation -import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.WalletRestoringFromSeedWordsNavigation import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowActivity import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthFragment import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeFragment import com.tari.android.wallet.ui.fragment.profile.WalletInfoFragment import com.tari.android.wallet.ui.fragment.restore.enterRestorationPassword.EnterRestorationPasswordFragment import com.tari.android.wallet.ui.fragment.restore.inputSeedWords.InputSeedWordsFragment -import com.tari.android.wallet.ui.fragment.restore.walletRestoringFromSeedWords.WalletRestoringFromSeedWordsFragment +import com.tari.android.wallet.ui.fragment.restore.walletRestoring.WalletRestoringFragment import com.tari.android.wallet.ui.fragment.send.addAmount.AddAmountFragment import com.tari.android.wallet.ui.fragment.send.addNote.AddNoteFragment import com.tari.android.wallet.ui.fragment.send.common.TransactionData @@ -101,9 +97,9 @@ import javax.inject.Singleton // TODO: move navigation logic to only the navigate() method and make all navigation methods private @Singleton class TariNavigator @Inject constructor( - val prefs: CorePrefRepository, - val tariSettingsSharedRepository: TariSettingsPrefRepository, + private val walletManager: WalletManager, private val yatAdapter: YatAdapter, + private val networkConnection: NetworkConnectionStateHandler, ) { lateinit var activity: CommonActivity<*, *> @@ -113,6 +109,7 @@ class TariNavigator @Inject constructor( is Navigation.EnterPinCodeNavigation -> addFragment(EnterPinCodeFragment.newInstance(navigation.behavior, navigation.stashedPin)) is Navigation.ChangeBiometrics -> addFragment(ChangeBiometricsFragment()) is Navigation.FeatureAuth -> addFragment(FeatureAuthFragment()) + is Navigation.SplashScreen -> toSplash(navigation.seedWords, navigation.clearTop) is ContactBookNavigation.ToAddContact -> toAddContact() is ContactBookNavigation.ToContactDetails -> toContactDetails(navigation.contact) is ContactBookNavigation.ToRequestTari -> toRequestTariFromContact(navigation.contact) @@ -124,8 +121,7 @@ class TariNavigator @Inject constructor( is ContactBookNavigation.ToAddPhoneContact -> toAddPhoneContact() is ContactBookNavigation.ToSelectTariUser -> addFragment(SelectUserContactFragment.newInstance()) is ChooseRestoreOptionNavigation.ToEnterRestorePassword -> toEnterRestorePassword() - is ChooseRestoreOptionNavigation.OnRestoreCompleted -> onRestoreCompleted() - is ChooseRestoreOptionNavigation.ToRestoreWithRecoveryPhrase -> toRestoreWithRecoveryPhrase() + is ChooseRestoreOptionNavigation.ToRestoreWithRecoveryPhrase -> addFragment(InputSeedWordsFragment.createFragment()) is AllSettingsNavigation.ToBugReporting -> DebugActivity.launch(activity, DebugNavigation.BugReport) is AllSettingsNavigation.ToMyProfile -> toMyProfile() is AllSettingsNavigation.ToAbout -> toAbout() @@ -140,11 +136,8 @@ class TariNavigator @Inject constructor( is AllSettingsNavigation.ToDataCollection -> addFragment(DataCollectionFragment()) is AllSettingsNavigation.ToThemeSelection -> toThemeSelection() is AllSettingsNavigation.ToRequestTari -> addFragment(RequestTariFragment.newInstance()) - is EnterRestorationPasswordNavigation.OnRestore -> onRestoreCompleted() - is InputSeedWordsNavigation.ToRestoreFormSeedWordsInProgress -> toRestoreFromSeedWordsInProgress() + is InputSeedWordsNavigation.ToRestoreFromSeeds -> addFragment(WalletRestoringFragment.newInstance()) is InputSeedWordsNavigation.ToBaseNodeSelection -> toBaseNodeSelection() - is WalletRestoringFromSeedWordsNavigation.OnRestoreCompleted -> onRestoreCompleted() - is WalletRestoringFromSeedWordsNavigation.OnRestoreFailed -> onBackPressed() is AddAmountNavigation.OnAmountExceedsActualAvailableBalance -> onAmountExceedsActualAvailableBalance() is AddAmountNavigation.ContinueToAddNote -> continueToAddNote(navigation.transactionData) is AddAmountNavigation.ContinueToFinalizing -> continueToFinalizeSendTx(navigation.transactionData) @@ -154,7 +147,6 @@ class TariNavigator @Inject constructor( is TxListNavigation.ToSendWithDeeplink -> toSendWithDeeplink(navigation.sendDeeplink) is TxListNavigation.ToUtxos -> toUtxos() is TxListNavigation.ToAllSettings -> toAllSettings() - is TxListNavigation.ToSplashScreen -> toSplash() is TxListNavigation.ToTransfer -> addFragment(TransferFragment()) is TxListNavigation.HomeTransactionHistory -> addFragment(HomeTransactionHistoryFragment()) is SendTxNavigation.OnSendTxFailure -> onSendTxFailure(navigation.isYat, navigation.txFailureReason) @@ -172,10 +164,12 @@ class TariNavigator @Inject constructor( } } - private fun toSplash() { - val intent = Intent(activity, OnboardingFlowActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK - activity.startActivity(intent) + private fun toSplash(seedWords: List? = null, clearTop: Boolean = true) { + activity.startActivity(Intent(activity, OnboardingFlowActivity::class.java).apply { + flags = if (clearTop) Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + else Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + putExtra(OnboardingFlowActivity.ARG_SEED_WORDS, seedWords?.toTypedArray()) + }) activity.finishAffinity() } @@ -186,25 +180,6 @@ class TariNavigator @Inject constructor( private fun toEnterRestorePassword() = addFragment(EnterRestorationPasswordFragment.newInstance()) - private fun toRestoreWithRecoveryPhrase() = addFragment(InputSeedWordsFragment.newInstance()) - - private fun toRestoreFromSeedWordsInProgress() = addFragment(WalletRestoringFromSeedWordsFragment.newInstance()) - - private fun onRestoreCompleted() { - // wallet restored, setup shared prefs accordingly - prefs.onboardingCompleted = true - prefs.onboardingStarted = true - prefs.onboardingAuthSetupStarted = true - prefs.onboardingAuthSetupCompleted = false - prefs.onboardingDisplayedAtHome = true - tariSettingsSharedRepository.isRestoredWallet = true - - activity.finish() - activity.startActivity(Intent(this.activity, OnboardingFlowActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - }) - } - fun onBackPressed() = activity.onBackPressed() fun toTxDetails(tx: Tx? = null, txId: TxId? = null) = activity.addFragment(TxDetailsFragment().apply { @@ -218,7 +193,7 @@ class TariNavigator @Inject constructor( fun toAllSettings() = (activity as HomeActivity).ui.viewPager.setCurrentItem(INDEX_SETTINGS, NO_SMOOTH_SCROLL) - fun toBackupSettings(withAnimation: Boolean) = addFragment(BackupSettingsFragment(), withAnimation = withAnimation) + fun toBackupSettings(withAnimation: Boolean) = addFragment(BackupSettingsFragment.newInstance(), withAnimation = withAnimation) private fun toDeleteWallet() = addFragment(DeleteWalletFragment()) @@ -310,7 +285,7 @@ class TariNavigator @Inject constructor( } private fun continueToAddNote(transactionData: TransactionData) { - if (EventBus.networkConnectionState.publishSubject.value != NetworkConnectionState.CONNECTED) { + if (!networkConnection.isNetworkConnected()) { showInternetConnectionErrorDialog(this.activity) return } @@ -355,7 +330,7 @@ class TariNavigator @Inject constructor( } private fun sendToUserByDeeplink(deeplink: DeepLink.Send) { - FFIWallet.instance?.getWalletAddress() + walletManager.walletInstance?.getWalletAddress() // TODO move all the logic beside of navigation from here val address = TariWalletAddress.fromBase58(deeplink.walletAddress) val contact = (activity as HomeActivity).viewModel.contactsRepository.getContactByAddress(address) val bundle = Bundle().apply { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt index ef3a93175..6e7b6eecc 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt @@ -40,8 +40,9 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.lifecycleScope import com.tari.android.wallet.R -import com.tari.android.wallet.application.walletManager.WalletStateHandler -import com.tari.android.wallet.data.WalletConfig +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.application.walletManager.doOnWalletFailed +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.databinding.ActivityOnboardingFlowBinding import com.tari.android.wallet.di.DiContainer.appComponent @@ -55,8 +56,8 @@ import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowMod import com.tari.android.wallet.ui.fragment.onboarding.createWallet.CreateWalletFragment import com.tari.android.wallet.ui.fragment.onboarding.inroduction.IntroductionFragment import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthFragment +import com.tari.android.wallet.ui.fragment.restore.walletRestoring.WalletRestoringFragment import com.tari.android.wallet.ui.fragment.settings.networkSelection.NetworkSelectionFragment -import com.tari.android.wallet.util.WalletUtil import kotlinx.coroutines.launch import javax.inject.Inject @@ -72,9 +73,6 @@ import javax.inject.Inject */ class OnboardingFlowActivity : CommonActivity(), OnboardingFlowListener { - @Inject - lateinit var walletConfig: WalletConfig - @Inject lateinit var corePrefRepository: CorePrefRepository @@ -82,7 +80,7 @@ class OnboardingFlowActivity : CommonActivity { + walletServiceLauncher.start(paperWalletSeeds) + + lifecycleScope.launch { + walletManager.doOnWalletRunning { + loadFragment(WalletRestoringFragment()) + } + } + } + corePrefRepository.onboardingAuthWasInterrupted -> { walletServiceLauncher.start() loadFragment(LocalAuthFragment()) @@ -104,7 +114,7 @@ class OnboardingFlowActivity : CommonActivity() { - @Inject - lateinit var walletStateHandler: WalletStateHandler - private val uiHandler = Handler(Looper.getMainLooper()) private var emojiIdContinueButtonHasBeenDisplayed = false diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/createWallet/CreateWalletViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/createWallet/CreateWalletViewModel.kt index c04629726..ff9bcb989 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/createWallet/CreateWalletViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/createWallet/CreateWalletViewModel.kt @@ -1,13 +1,13 @@ package com.tari.android.wallet.ui.fragment.onboarding.createWallet -import androidx.lifecycle.viewModelScope +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.event.EffectChannelFlow +import com.tari.android.wallet.extension.launchOnIo +import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.fragment.onboarding.createWallet.CreateWalletModel.Effect -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.launch import javax.inject.Inject class CreateWalletViewModel : CommonViewModel() { @@ -28,9 +28,9 @@ class CreateWalletViewModel : CommonViewModel() { } fun waitUntilWalletCreated() { - viewModelScope.launch(Dispatchers.IO) { - walletStateHandler.doOnWalletRunning { - viewModelScope.launch(Dispatchers.Main) { + launchOnIo { + walletManager.doOnWalletRunning { + launchOnMain { _effect.send(Effect.StartCheckmarkAnimation) } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt index 2ad13f45d..5e5429b9d 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt @@ -1,8 +1,10 @@ package com.tari.android.wallet.ui.fragment.onboarding.localAuth import androidx.lifecycle.viewModelScope +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.event.EffectChannelFlow import com.tari.android.wallet.extension.addTo +import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationService import com.tari.android.wallet.ui.common.CommonViewModel @@ -10,7 +12,6 @@ import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthModel.Effect import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthModel.SecureState import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -49,12 +50,12 @@ class LocalAuthViewModel : CommonViewModel() { fun proceedToMain() { viewModelScope.launch { - walletStateHandler.doOnWalletRunning { + walletManager.doOnWalletRunning { securityPrefRepository.isAuthenticated = true sharedPrefsRepository.onboardingAuthSetupCompleted = true backupManager.backupNow() - navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.CreateConfirm)) - viewModelScope.launch(Dispatchers.Main) { + tariNavigator.navigate(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.CreateConfirm)) + launchOnMain { _effect.send(Effect.OnAuthSuccess) } } @@ -62,7 +63,7 @@ class LocalAuthViewModel : CommonViewModel() { } fun goToEnterPinCode() { - navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.Create)) + tariNavigator.navigate(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.Create)) } private fun updateState() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoViewModel.kt index b5d05b707..451205aaf 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoViewModel.kt @@ -5,7 +5,7 @@ import android.graphics.Bitmap import com.tari.android.wallet.R import com.tari.android.wallet.application.YatAdapter import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.extension.launchOnIo import com.tari.android.wallet.extension.launchOnMain @@ -38,7 +38,7 @@ class WalletInfoViewModel : CommonViewModel() { lateinit var yatAdapter: YatAdapter @Inject - lateinit var deeplinkHandler: DeeplinkHandler + lateinit var deeplinkManager: DeeplinkManager @Inject lateinit var contactUtil: ContactUtil @@ -56,7 +56,7 @@ class WalletInfoViewModel : CommonViewModel() { ) val uiState = _uiState.asStateFlow() - private val shareProfileDeeplink = deeplinkHandler.getDeeplink( + private val shareProfileDeeplink = deeplinkManager.getDeeplinkString( DeepLink.UserProfile( tariAddress = corePrefRepository.walletAddressBase58.orEmpty(), alias = contactUtil.normalizeAlias(uiState.value.alias, corePrefRepository.walletAddress), diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt deleted file mode 100644 index 1315c5c74..000000000 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tari.android.wallet.ui.fragment.qr - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.tari.android.wallet.R -import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel -import com.tari.android.wallet.extension.launchOnIo -import com.tari.android.wallet.extension.launchOnMain -import com.tari.android.wallet.model.TariWalletAddress -import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.common.SingleLiveEvent -import com.tari.android.wallet.ui.fragment.home.HomeActivity -import com.tari.android.wallet.util.shortString -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import javax.inject.Inject - -class QRScannerViewModel : CommonViewModel() { - - @Inject - lateinit var deeplinkHandler: DeeplinkHandler - - init { - component.inject(this) - } - - val qrScannerSource: MutableLiveData = MutableLiveData() - - val scanError: MutableLiveData = MutableLiveData(false) - - val alternativeText: MutableLiveData = MutableLiveData("") - - val scannedDeeplink: MutableLiveData = MutableLiveData() - - val navigationBackWithData: SingleLiveEvent = SingleLiveEvent() - - val deeplinkViewModel: DeeplinkViewModel - get() = HomeActivity.instance.get()?.deeplinkViewModel!! - - val proceedScan = SingleLiveEvent() - - fun init(source: QrScannerSource) { - qrScannerSource.postValue(source) - } - - fun onAlternativeApply() { - backPressed.postValue(Unit) - executeWithDelay(deeplinkViewModel) { - deeplinkViewModel.executeRawDeeplink(scannedDeeplink.value!!) - } - } - - private fun executeWithDelay(commonViewModel: CommonViewModel, action: () -> Unit) { - commonViewModel.viewModelScope.launch(Dispatchers.IO) { - delay(500) - commonViewModel.viewModelScope.launch(Dispatchers.Main) { - action() - } - } - } - - fun onAlternativeDeny() { - alternativeText.postValue("") - proceedScan.postValue(Unit) - } - - fun onScanResult(text: String?) { - val deeplink = deeplinkHandler.handle(text.orEmpty()) - if (deeplink == null) { - scanError.postValue(true) - } else { - handleDeeplink(deeplink) - } - } - - private fun handleDeeplink(deepLink: DeepLink) { - scannedDeeplink.postValue(deepLink) - - when (qrScannerSource.value) { - QrScannerSource.None, - QrScannerSource.Home -> setAlternativeText(deepLink) - - QrScannerSource.TransactionSend -> { - when (deepLink) { - is DeepLink.Send, - is DeepLink.UserProfile -> navigateBack(deepLink) - - is DeepLink.Contacts, - is DeepLink.TorBridges, - is DeepLink.AddBaseNode -> setAlternativeText(deepLink) - } - } - - QrScannerSource.AddContact -> { - when (deepLink) { - is DeepLink.UserProfile -> navigateBack(deepLink) - - is DeepLink.Send, - is DeepLink.Contacts, - is DeepLink.TorBridges, - is DeepLink.AddBaseNode -> setAlternativeText(deepLink) - } - } - - QrScannerSource.ContactBook -> { - when (deepLink) { - is DeepLink.Send, - is DeepLink.UserProfile, - is DeepLink.Contacts -> navigateBack(deepLink) - - is DeepLink.TorBridges, - is DeepLink.AddBaseNode -> setAlternativeText(deepLink) - } - } - - QrScannerSource.TorBridges -> { - when (deepLink) { - is DeepLink.Send, - is DeepLink.UserProfile, - is DeepLink.AddBaseNode, - is DeepLink.Contacts -> setAlternativeText(deepLink) - - is DeepLink.TorBridges -> navigateBack(deepLink) - } - } - - null -> Unit - } - } - - private fun setAlternativeText(deepLink: DeepLink) { - launchOnIo { - val text = when (deepLink) { - is DeepLink.Send -> { - val walletAddress = TariWalletAddress.fromBase58OrNull(deepLink.walletAddress) ?: return@launchOnIo - resourceManager.getString(R.string.qr_code_scanner_labels_actions_transaction_send, walletAddress.shortString()) - } - - is DeepLink.UserProfile -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_profile) - is DeepLink.Contacts -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_contacts) - is DeepLink.AddBaseNode -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_base_node_add) - is DeepLink.TorBridges -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_tor_bridges) - } - launchOnMain { - alternativeText.postValue(text) - } - } - } - - private fun navigateBack(deepLink: DeepLink) { - navigationBackWithData.postValue(deeplinkHandler.getDeeplink(deepLink)) - } - - fun onRetry() { - proceedScan.postValue(Unit) - scanError.postValue(false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QrScannerActivity.kt similarity index 79% rename from app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt rename to app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QrScannerActivity.kt index b228c184f..0eb1c1252 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QrScannerActivity.kt @@ -49,13 +49,13 @@ import com.budiyev.android.codescanner.ErrorCallback import com.budiyev.android.codescanner.ScanMode import com.google.zxing.BarcodeFormat import com.tari.android.wallet.R +import com.tari.android.wallet.application.deeplinks.DeepLink import com.tari.android.wallet.databinding.ActivityQrScannerBinding import com.tari.android.wallet.di.DiContainer.appComponent -import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.extension.collectFlow import com.tari.android.wallet.ui.common.CommonActivity import com.tari.android.wallet.ui.component.tari.toast.TariToast import com.tari.android.wallet.ui.component.tari.toast.TariToastArgs -import com.tari.android.wallet.ui.extension.serializable import com.tari.android.wallet.ui.extension.setVisible /** @@ -63,7 +63,7 @@ import com.tari.android.wallet.ui.extension.setVisible * * @author The Tari Development Team */ -class QRScannerActivity : CommonActivity() { +class QrScannerActivity : CommonActivity() { companion object { /** @@ -71,21 +71,15 @@ class QRScannerActivity : CommonActivity(QR_DATA_SOURCE) - viewModel.init(data ?: QrScannerSource.None) subscribeUI() setupUi() @@ -113,16 +103,18 @@ class QRScannerActivity : CommonActivity + ui.errorContainer.setVisible(uiState.scanError) + ui.alternativeText.text = uiState.alternativeText + ui.alternativeContainer.setVisible(uiState.alternativeText.isNotEmpty()) } - observe(navigationBackWithData) { doNavigationBack(it) } - - observe(proceedScan) { codeScanner.startPreview() } + collectFlow(effect) { effect -> + when (effect) { + is QrScannerModel.Effect.FinishWithResult -> finishWithResult(effect.deepLink) + is QrScannerModel.Effect.ProceedScan -> codeScanner.startPreview() + } + } } private fun setupUi() = with(ui) { @@ -130,7 +122,7 @@ class QRScannerActivity : CommonActivity = _uiState.asStateFlow() + + private val _effect = EffectChannelFlow() + val effect: Flow = _effect.flow + + private val qrScannerSource: QrScannerSource = savedState.get(EXTRA_QR_DATA_SOURCE) ?: QrScannerSource.None + + fun onAlternativeApply(context: Activity) { + backPressed.postValue(Unit) + launchOnIo { + delay(500) + launchOnMain { + deeplinkManager.executeRawDeeplink(context, uiState.value.scannedDeeplink!!) + } + } + } + + fun onAlternativeDeny() { + launchOnMain { + _uiState.update { it.copy(alternativeText = "") } + _effect.send(QrScannerModel.Effect.ProceedScan) + } + } + + fun onScanResult(text: String?) { + val deeplink = deeplinkManager.parseDeepLink(text.orEmpty()) + if (deeplink == null) { + _uiState.update { it.copy(scanError = true) } + } else { + handleDeeplink(deeplink) + } + } + + private fun handleDeeplink(deepLink: DeepLink) { + _uiState.update { it.copy(scannedDeeplink = deepLink) } + + when (qrScannerSource) { + QrScannerSource.None, + QrScannerSource.Home -> { + when (deepLink) { + is DeepLink.Send, + is DeepLink.UserProfile, + is DeepLink.Contacts, + is DeepLink.TorBridges, + is DeepLink.AddBaseNode -> setAlternativeText(deepLink) + + is DeepLink.PaperWallet -> returnResult(deepLink) + } + } + + QrScannerSource.TransactionSend -> { + when (deepLink) { + is DeepLink.Send, + is DeepLink.UserProfile, + is DeepLink.PaperWallet -> returnResult(deepLink) + + is DeepLink.Contacts, + is DeepLink.TorBridges, + is DeepLink.AddBaseNode -> setAlternativeText(deepLink) + } + } + + QrScannerSource.AddContact -> { + when (deepLink) { + is DeepLink.UserProfile, + is DeepLink.PaperWallet -> returnResult(deepLink) + + is DeepLink.Send, + is DeepLink.Contacts, + is DeepLink.TorBridges, + is DeepLink.AddBaseNode -> setAlternativeText(deepLink) + } + } + + QrScannerSource.ContactBook -> { + when (deepLink) { + is DeepLink.Send, + is DeepLink.UserProfile, + is DeepLink.Contacts, + is DeepLink.PaperWallet -> returnResult(deepLink) + + is DeepLink.TorBridges, + is DeepLink.AddBaseNode -> setAlternativeText(deepLink) + + } + } + + QrScannerSource.TorBridges -> { + when (deepLink) { + is DeepLink.Send, + is DeepLink.UserProfile, + is DeepLink.AddBaseNode, + is DeepLink.Contacts, + is DeepLink.PaperWallet -> setAlternativeText(deepLink) + + is DeepLink.TorBridges -> returnResult(deepLink) + } + } + + QrScannerSource.PaperWallet -> { + when (deepLink) { + is DeepLink.Send, + is DeepLink.UserProfile, + is DeepLink.Contacts, + is DeepLink.TorBridges, + is DeepLink.AddBaseNode -> _uiState.update { it.copy(scanError = true) } + + is DeepLink.PaperWallet -> returnResult(deepLink) + } + } + } + } + + private fun setAlternativeText(deepLink: DeepLink) { + val text = when (deepLink) { + is DeepLink.Send -> { + val walletAddress = TariWalletAddress.fromBase58OrNull(deepLink.walletAddress) ?: return + resourceManager.getString(R.string.qr_code_scanner_labels_actions_transaction_send, walletAddress.shortString()) + } + + is DeepLink.UserProfile -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_profile) + is DeepLink.Contacts -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_contacts) + is DeepLink.AddBaseNode -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_base_node_add) + is DeepLink.TorBridges -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_tor_bridges) + is DeepLink.PaperWallet -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_paper_wallet) // should never show. Show PW dialog instead + } + _uiState.update { it.copy(alternativeText = text) } + } + + private fun returnResult(deepLink: DeepLink) { + launchOnMain { _effect.send(QrScannerModel.Effect.FinishWithResult(deepLink)) } + } + + fun onRetry() { + launchOnMain { + _effect.send(QrScannerModel.Effect.ProceedScan) + _uiState.update { it.copy(scanError = false) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt index 0a579043b..4e8ab46d0 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt @@ -37,7 +37,6 @@ import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import com.tari.android.wallet.R -import com.tari.android.wallet.application.MigrationManager import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsPrefRepository import com.tari.android.wallet.databinding.ActivityWalletBackupBinding @@ -58,15 +57,6 @@ class WalletRestoreActivity : CommonActivity() { @@ -71,49 +76,59 @@ class ChooseRestoreOptionFragment : CommonFragment(QrScannerActivity.EXTRA_DEEPLINK) ?: return + viewModel.handleDeeplink(qrDeepLink) + } else { + viewModel.onActivityResult(requestCode, resultCode, data) + } } private fun setupUI() = with(ui) { - restoreWithRecoveryPhraseCtaView.setOnClickListener { viewModel.navigation.postValue(Navigation.ChooseRestoreOptionNavigation.ToRestoreWithRecoveryPhrase) } - } - - private fun startRecovery(options: BackupOptions) { - viewModel.startRestore(options) - viewModel.backupManager.setupStorage(options, this) + restoreWithRecoveryPhraseCtaView.setOnClickListener { viewModel.onRecoveryPhraseClicked() } + restoreWithPaperWalletCtaView.setOnClickListener { viewModel.onPaperWalletClicked(this@ChooseRestoreOptionFragment) } } private fun observeUI() = with(viewModel) { - observe(state) { processState(it) } - - observe(options) { initOptions(it) } + collectFlow(uiState) { uiState -> + initOptions(uiState.backupOptions) + uiState.selectedOption?.let { updateProgress(it, uiState.isStarted) } + + if (uiState.paperWalletProgress) { + ui.restoreWithPaperWalletCtaView.isEnabled = false + ui.restoreWithPaperWalletProgressView.visible() + ui.restoreWithPaperWalletArrow.gone() + } else { + ui.restoreWithPaperWalletCtaView.isEnabled = true + ui.restoreWithPaperWalletProgressView.gone() + ui.restoreWithPaperWalletArrow.visible() + } + } } private fun initOptions(options: List) { + ui.optionsContainer.removeAllViews() for (option in options) { val view = RecoveryOptionView(requireContext()).apply { viewLifecycle = viewLifecycleOwner - ui.restoreWalletCtaView.setOnClickListener { startRecovery(option.type) } + ui.restoreWalletCtaView.setOnClickListener { + this@ChooseRestoreOptionFragment.viewModel.startRecovery( + selectedOption = option.type, + hostFragment = this@ChooseRestoreOptionFragment, + ) + } init(option.type) } ui.optionsContainer.addView(view) } } - private fun processState(state: ChooseRestoreOptionState) { - when (state) { - is ChooseRestoreOptionState.BeginProgress -> updateProgress(state.backupOptions, true) - is ChooseRestoreOptionState.EndProgress -> updateProgress(state.backupOptions, false) - } - } - - - private fun updateProgress(backupOptions: BackupOptions, isStarted: Boolean) { + private fun updateProgress(backupOption: BackupOption, isStarted: Boolean) { blockingBackPressDispatcher.isEnabled = isStarted - getBackupOptionView(backupOptions)?.updateLoading(isStarted) + getBackupOptionView(backupOption)?.updateLoading(isStarted) } - private fun getBackupOptionView(backupOptions: BackupOptions): RecoveryOptionView? = + private fun getBackupOptionView(backupOptions: BackupOption): RecoveryOptionView? = ui.optionsContainer.children.mapNotNull { it as? RecoveryOptionView }.firstOrNull { it.viewModel.option == backupOptions } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionModel.kt new file mode 100644 index 000000000..44014ab70 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionModel.kt @@ -0,0 +1,15 @@ +package com.tari.android.wallet.ui.fragment.restore.chooseRestoreOption + +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptionDto + +object ChooseRestoreOptionModel { + data class UiState( + val backupOptions: List = emptyList(), + + val selectedOption: BackupOption? = null, + val isStarted: Boolean = false, + + val paperWalletProgress: Boolean = false, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionState.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionState.kt deleted file mode 100644 index 781b9597a..000000000 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tari.android.wallet.ui.fragment.restore.chooseRestoreOption - -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions - -sealed class ChooseRestoreOptionState(val backupOptions: BackupOptions) { - class BeginProgress(backupOptions: BackupOptions) : ChooseRestoreOptionState(backupOptions) - - class EndProgress(backupOptions: BackupOptions) : ChooseRestoreOptionState(backupOptions) -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionViewModel.kt index 8314a8662..f817e670d 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/ChooseRestoreOptionViewModel.kt @@ -1,12 +1,16 @@ package com.tari.android.wallet.ui.fragment.restore.chooseRestoreOption import android.content.Intent -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope +import androidx.fragment.app.Fragment import com.tari.android.wallet.R +import com.tari.android.wallet.application.deeplinks.DeepLink +import com.tari.android.wallet.application.walletManager.WalletFileUtil +import com.tari.android.wallet.application.walletManager.doOnWalletFailed +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.backup.BackupPrefRepository +import com.tari.android.wallet.extension.launchOnIo +import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.infrastructure.backup.BackupFileIsEncryptedException import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.infrastructure.backup.BackupStorageAuthRevokedException @@ -17,14 +21,19 @@ import com.tari.android.wallet.model.WalletError import com.tari.android.wallet.model.throwIf import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.common.SingleLiveEvent -import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs +import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule +import com.tari.android.wallet.ui.dialog.modular.modules.input.InputModule import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptionDto -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions -import com.tari.android.wallet.util.WalletUtil -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import com.tari.android.wallet.ui.fragment.qr.QrScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerSource +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import java.io.IOException import javax.inject.Inject @@ -42,51 +51,67 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { @Inject lateinit var walletConfig: WalletConfig - private val _state = SingleLiveEvent() - val state: LiveData = _state - - val options = MutableLiveData>() - init { component.inject(this) + } - options.postValue(backupPrefRepository.getOptionList) - - viewModelScope.launch(Dispatchers.IO) { - walletStateHandler.doOnWalletRunning { - if (WalletUtil.walletExists(walletConfig) && state.value != null) { - backupPrefRepository.restoredTxs?.let { - if (it.utxos.orEmpty().isEmpty()) return@let + private val _uiState = MutableStateFlow(ChooseRestoreOptionModel.UiState(backupOptions = backupPrefRepository.getOptionList)) + val uiState = _uiState.asStateFlow() - val tariWalletAddress = TariWalletAddress.fromBase58(it.sourceBase58) - val message = resourceManager.getString(R.string.backup_restored_tx) - val error = WalletError() - walletService.restoreWithUnbindedOutputs(it.utxos, tariWalletAddress, message, error) - throwIf(error) - } + init { + launchOnIo { + walletManager.doOnWalletRunning { + uiState.value.selectedOption?.let { selectedOption -> + if (WalletFileUtil.walletExists(walletConfig)) { + backupPrefRepository.restoredTxs?.takeIf { it.utxos.isNotEmpty() }?.let { restoredTxs -> + val tariWalletAddress = TariWalletAddress.fromBase58(restoredTxs.sourceBase58) + val message = resourceManager.getString(R.string.backup_restored_tx) + val error = WalletError() + walletService.restoreWithUnbindedOutputs(restoredTxs.utxos, tariWalletAddress, message, error) + throwIf(error) + } - val dto = backupPrefRepository.getOptionDto(state.value!!.backupOptions)!!.copy(isEnable = true) - backupPrefRepository.updateOption(dto) - backupManager.backupNow() + val dto = backupPrefRepository.getOptionDto(selectedOption).copy(isEnable = true) + backupPrefRepository.updateOption(dto) + backupManager.backupNow() - navigation.postValue(Navigation.ChooseRestoreOptionNavigation.OnRestoreCompleted) + walletManager.onWalletRestored() + tariNavigator.navigate(Navigation.SplashScreen(clearTop = false)) + } } } } - viewModelScope.launch(Dispatchers.IO) { - walletStateHandler.doOnWalletFailed { + launchOnIo { + walletManager.doOnWalletFailed { handleException(WalletStartFailedException(it)) } } } - fun startRestore(options: BackupOptions) { - _state.postValue(ChooseRestoreOptionState.BeginProgress(options)) + fun startRecovery(selectedOption: BackupOption, hostFragment: Fragment) { + _uiState.update { it.copy(isStarted = true) } + backupManager.setupStorage(selectedOption, hostFragment) + } + + fun onRecoveryPhraseClicked() { + tariNavigator.navigate(Navigation.ChooseRestoreOptionNavigation.ToRestoreWithRecoveryPhrase) + } + + fun onPaperWalletClicked(fragment: Fragment) { + QrScannerActivity.startScanner(fragment, QrScannerSource.PaperWallet) + } + + fun handleDeeplink(qrDeepLink: DeepLink) { + if (qrDeepLink is DeepLink.PaperWallet) { + showPaperWalletDialog(qrDeepLink) + } else { + showInvalidQrDialog() + } } fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { try { if (backupManager.onSetupActivityResult(requestCode, resultCode, data)) { restoreFromBackup() @@ -94,7 +119,7 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { } catch (exception: Exception) { logger.i(exception.message + "Backup storage setup failed") backupManager.signOut() - _state.postValue(ChooseRestoreOptionState.EndProgress(backupManager.currentOption!!)) + _uiState.update { it.copy(isStarted = false) } showAuthFailedDialog() } } @@ -104,7 +129,7 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { try { // try to restore with no password backupManager.restoreLatestBackup() - viewModelScope.launch(Dispatchers.Main) { + launchOnMain { walletServiceLauncher.start() } } catch (exception: Throwable) { @@ -112,6 +137,17 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { } } + private fun restoreFromPaperWallet(seedWords: List) { + _uiState.update { it.copy(paperWalletProgress = true) } + walletServiceLauncher.start(seedWords) + launchOnIo { + walletManager.doOnWalletRunning { + _uiState.update { it.copy(paperWalletProgress = false) } + tariNavigator.navigate(Navigation.InputSeedWordsNavigation.ToRestoreFromSeeds) + } + } + } + private suspend fun handleException(exception: Throwable) { when (exception) { is BackupStorageAuthRevokedException -> { @@ -132,8 +168,8 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { is WalletStartFailedException -> { logger.i("Restore failed: wallet start failed") - viewModelScope.launch(Dispatchers.Main) { - walletServiceLauncher.stopAndDelete() + launchOnMain { + walletManager.deleteWallet() } val cause = WalletError(exception.cause) if (cause == WalletError.DatabaseDataError) { @@ -158,34 +194,95 @@ class ChooseRestoreOptionViewModel : CommonViewModel() { } } - _state.postValue(ChooseRestoreOptionState.EndProgress(backupManager.currentOption!!)) + _uiState.update { it.copy(isStarted = false) } } - private fun showBackupFileNotFoundDialog() { + private fun showPaperWalletDialog(deepLink: DeepLink.PaperWallet) { showModularDialog( - SimpleDialogArgs( - title = resourceManager.getString(R.string.restore_wallet_error_title), - description = resourceManager.getString(R.string.restore_wallet_error_file_not_found), - onClose = { backPressed.call() }, - ).getModular(resourceManager) + HeadModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_title)), + BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_body)), + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_restore_button), ButtonStyle.Normal) { + hideDialog() + showEnterPassphraseDialog(deepLink) + }, + ButtonModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_do_not_restore_button), ButtonStyle.Close), + ) + } + + private fun showEnterPassphraseDialog(deeplink: DeepLink.PaperWallet) { + var saveAction: () -> Boolean = { false } + + val headModule = HeadModule( + title = resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_title), + rightButtonTitle = resourceManager.getString(R.string.common_done), + rightButtonAction = { saveAction() }, + ) + + val bodyModule = BodyModule(resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_body)) + + val passphraseModule = InputModule( + value = "", + hint = resourceManager.getString(R.string.restore_wallet_paper_wallet_enter_passphrase_hint), + isFirst = true, + isEnd = true, + onDoneAction = { saveAction() }, + ) + + saveAction = { + val seeds = deeplink.seedWords(passphraseModule.value.trim()) + if (seeds != null) { + hideDialog() + restoreFromPaperWallet(seeds) + } else { + showPaperWalletErrorDialog() + } + true + } + + showInputModalDialog( + ModularDialogArgs( + modules = listOf( + headModule, + bodyModule, + passphraseModule, + ), + ) + ) + } + + private fun showPaperWalletErrorDialog() { + showSimpleDialog( + title = resourceManager.getString(R.string.restore_wallet_paper_wallet_error_title), + description = resourceManager.getString(R.string.restore_wallet_paper_wallet_error_body), + ) + } + + private fun showBackupFileNotFoundDialog() { + showSimpleDialog( + title = resourceManager.getString(R.string.restore_wallet_error_title), + description = resourceManager.getString(R.string.restore_wallet_error_file_not_found), + onClose = { backPressed.call() }, ) } private fun showRestoreFailedDialog(message: String? = null) { - showModularDialog( - SimpleDialogArgs( - title = resourceManager.getString(R.string.restore_wallet_error_title), - description = resourceManager.getString(R.string.restore_wallet_error_desc, message.orEmpty()) - ).getModular(resourceManager) + showSimpleDialog( + title = resourceManager.getString(R.string.restore_wallet_error_title), + description = resourceManager.getString(R.string.restore_wallet_error_desc, message.orEmpty()), ) } private fun showAuthFailedDialog() { - showModularDialog( - SimpleDialogArgs( - title = resourceManager.getString(R.string.restore_wallet_error_title), - description = resourceManager.getString(R.string.back_up_wallet_storage_setup_error_desc), - ).getModular(resourceManager) + showSimpleDialog( + title = resourceManager.getString(R.string.restore_wallet_error_title), + description = resourceManager.getString(R.string.back_up_wallet_storage_setup_error_desc), + ) + } + + private fun showInvalidQrDialog() { + showSimpleDialog( + title = resourceManager.getString(R.string.restore_wallet_invalid_qr_code), + description = resourceManager.getString(R.string.restore_wallet_invalid_qr_code_description), ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionView.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionView.kt index 3f824f80d..ff2b8b50b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionView.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionView.kt @@ -9,7 +9,7 @@ import com.tari.android.wallet.databinding.ViewRestoreOptionBinding import com.tari.android.wallet.ui.component.common.CommonView import com.tari.android.wallet.ui.extension.gone import com.tari.android.wallet.ui.extension.setVisible -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption class RecoveryOptionView : CommonView { @@ -26,11 +26,11 @@ class RecoveryOptionView : CommonView R.string.back_up_wallet_restore_with_google_drive - BackupOptions.Local -> R.string.back_up_wallet_restore_with_local_files - BackupOptions.Dropbox -> R.string.back_up_wallet_restore_with_dropbox + BackupOption.Google -> R.string.back_up_wallet_restore_with_google_drive + BackupOption.Local -> R.string.back_up_wallet_restore_with_local_files + BackupOption.Dropbox -> R.string.back_up_wallet_restore_with_dropbox } ui.title.text = context.getString(text) bindViewModel(RecoveryOptionViewModel().apply { this.option = option }) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionViewModel.kt index 05d4517d0..b4f1465ab 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/chooseRestoreOption/option/RecoveryOptionViewModel.kt @@ -1,9 +1,9 @@ package com.tari.android.wallet.ui.fragment.restore.chooseRestoreOption.option import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption class RecoveryOptionViewModel : CommonViewModel() { - var option: BackupOptions? = null + var option: BackupOption? = null } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/enterRestorationPassword/EnterRestorationPasswordViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/enterRestorationPassword/EnterRestorationPasswordViewModel.kt index c192f1020..1e42c3da5 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/enterRestorationPassword/EnterRestorationPasswordViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/enterRestorationPassword/EnterRestorationPasswordViewModel.kt @@ -1,10 +1,13 @@ package com.tari.android.wallet.ui.fragment.restore.enterRestorationPassword import androidx.lifecycle.LiveData -import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R +import com.tari.android.wallet.application.walletManager.doOnWalletFailed +import com.tari.android.wallet.application.walletManager.doOnWalletRunning import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.backup.BackupPrefRepository +import com.tari.android.wallet.extension.launchOnIo +import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.infrastructure.backup.WalletStartFailedException import com.tari.android.wallet.service.service.WalletServiceLauncher @@ -12,9 +15,7 @@ import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import com.tari.android.wallet.util.WalletUtil -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import com.tari.android.wallet.application.walletManager.WalletFileUtil import java.security.GeneralSecurityException import javax.inject.Inject @@ -35,20 +36,21 @@ class EnterRestorationPasswordViewModel : CommonViewModel() { init { component.inject(this) - viewModelScope.launch { - walletStateHandler.doOnWalletRunning { - if (WalletUtil.walletExists(walletConfig)) { + launchOnIo { + walletManager.doOnWalletRunning { + if (WalletFileUtil.walletExists(walletConfig)) { val dto = backupSettingsRepository.getOptionDto(backupManager.currentOption!!)!!.copy(isEnable = true) backupSettingsRepository.updateOption(dto) backupManager.backupNow() - navigation.postValue(Navigation.EnterRestorationPasswordNavigation.OnRestore) + walletManager.onWalletRestored() + tariNavigator.navigate(Navigation.SplashScreen(clearTop = false)) } } } - viewModelScope.launch { - walletStateHandler.doOnWalletFailed { + launchOnIo { + walletManager.doOnWalletFailed { handleRestorationFailure(WalletStartFailedException(it)) } } @@ -59,7 +61,7 @@ class EnterRestorationPasswordViewModel : CommonViewModel() { fun onBack() { backPressed.postValue(Unit) - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { backupManager.signOut() } } @@ -70,11 +72,11 @@ class EnterRestorationPasswordViewModel : CommonViewModel() { } private fun performRestoration(password: String) { - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { try { backupManager.restoreLatestBackup(password) backupSettingsRepository.backupPassword = password - viewModelScope.launch(Dispatchers.Main) { + launchOnMain { walletServiceLauncher.start() } } catch (exception: Throwable) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/inputSeedWords/InputSeedWordsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/inputSeedWords/InputSeedWordsFragment.kt index c8cb1c60a..1fdabf345 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/inputSeedWords/InputSeedWordsFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/inputSeedWords/InputSeedWordsFragment.kt @@ -82,8 +82,8 @@ class InputSeedWordsFragment : CommonFragment() - @Inject - lateinit var seedPhraseRepository: SeedPhraseRepository - @Inject lateinit var walletServiceLauncher: WalletServiceLauncher @@ -68,7 +66,7 @@ class InputSeedWordsViewModel : CommonViewModel() { val isInProgress: LiveData = _inProgress val isAllEntered: LiveData = _words.map { words -> - words.all { it.text.value!!.isNotEmpty() } && words.size == SeedPhrase.SeedPhraseLength + words.all { it.text.value!!.isNotEmpty() } && words.size == SeedPhrase.SEED_PHRASE_LENGTH } private val _continueButtonState = MediatorLiveData() @@ -97,19 +95,18 @@ class InputSeedWordsViewModel : CommonViewModel() { fun startRestoringWallet() { val words = _words.value!!.map { it.text.value!! } - val seedPhrase = SeedPhrase() - val result = seedPhrase.init(words) + when (SeedPhrase.create(words)) { + is SeedPhrase.SeedPhraseCreationResult.Success -> startRestoring(words) - if (result == SeedPhrase.SeedPhraseCreationResult.Success) { - seedPhraseRepository.save(seedPhrase) - startRestoring() - } else { - handleSeedPhraseResult(result) + is SeedPhrase.SeedPhraseCreationResult.SeedPhraseNotCompleted -> onError(RestorationError.SeedPhraseTooShort(resourceManager)) + is SeedPhrase.SeedPhraseCreationResult.Failed -> onError(RestorationError.Unknown(resourceManager)) + is SeedPhrase.SeedPhraseCreationResult.InvalidSeedPhrase, + is SeedPhrase.SeedPhraseCreationResult.InvalidSeedWord -> onError(RestorationError.Invalid(resourceManager)) } } private fun loadSuggestions() { - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { val mnemonic = FFISeedWords.getMnemomicWordList(FFISeedWords.Language.English) val size = mnemonic.getLength() for (i in 0 until size) { @@ -118,11 +115,11 @@ class InputSeedWordsViewModel : CommonViewModel() { } } - private fun startRestoring() { + private fun startRestoring(seedWords: List) { _inProgress.postValue(true) - viewModelScope.launch(Dispatchers.IO) { - walletStateHandler.doOnWalletFailed { exception -> + launchOnIo { + walletManager.doOnWalletFailed { exception -> if (WalletError(exception) == WalletError.NoError) { onError(RestorationError.Unknown(resourceManager)) } else { @@ -133,36 +130,28 @@ class InputSeedWordsViewModel : CommonViewModel() { } } - viewModelScope.launch(Dispatchers.IO) { - walletStateHandler.doOnWalletRunning { - navigation.postValue(Navigation.InputSeedWordsNavigation.ToRestoreFormSeedWordsInProgress) + launchOnIo { + walletManager.doOnWalletRunning { + tariNavigator.navigate(Navigation.InputSeedWordsNavigation.ToRestoreFromSeeds) _inProgress.postValue(false) } } - customBaseNodeState.value.customBaseNode?.let { - baseNodesManager.addUserBaseNode(it) - baseNodesManager.setBaseNode(it) + if (DebugConfig.selectBaseNodeEnabled) { + customBaseNodeState.value.customBaseNode?.let { + baseNodesManager.addUserBaseNode(it) + baseNodesManager.setBaseNode(it) + walletManager.syncBaseNode() + } } - walletServiceLauncher.start() - } - - private fun handleSeedPhraseResult(result: SeedPhrase.SeedPhraseCreationResult) { - val errorDialogArgs = when (result) { - is SeedPhrase.SeedPhraseCreationResult.Failed -> RestorationError.Unknown(resourceManager) - SeedPhrase.SeedPhraseCreationResult.InvalidSeedPhrase, - SeedPhrase.SeedPhraseCreationResult.InvalidSeedWord -> RestorationError.Invalid(resourceManager) - SeedPhrase.SeedPhraseCreationResult.SeedPhraseNotCompleted -> RestorationError.SeedPhraseTooShort(resourceManager) - else -> RestorationError.Unknown(resourceManager) - } - onError(errorDialogArgs) + walletServiceLauncher.start(seedWords) } private fun onError(restorationError: RestorationError) = showModularDialog(restorationError.args.getModular(resourceManager)) private fun clear() { - walletServiceLauncher.stopAndDelete() + walletManager.deleteWallet() compositeDisposable.dispose() compositeDisposable = CompositeDisposable() } @@ -208,7 +197,7 @@ class InputSeedWordsViewModel : CommonViewModel() { list[index].text.value = formattedText[0] for ((formattedIndex, item) in formattedText.drop(1).withIndex()) { val nextIndex = index + formattedIndex + 1 - if (list.size < SeedPhrase.SeedPhraseLength) { + if (list.size < SeedPhrase.SEED_PHRASE_LENGTH) { addWord(nextIndex, item) } } @@ -225,7 +214,7 @@ class InputSeedWordsViewModel : CommonViewModel() { fun getFocusToNextElement(currentIndex: Int) { val list = _words.value!! - if (list.isEmpty() || list.last().text.value!!.isNotEmpty() && list.size < SeedPhrase.SeedPhraseLength) { + if (list.isEmpty() || list.last().text.value!!.isNotEmpty() && list.size < SeedPhrase.SEED_PHRASE_LENGTH) { WordItemViewModel.create("", mnemonicList).apply { list.add(this) reindex() @@ -250,7 +239,7 @@ class InputSeedWordsViewModel : CommonViewModel() { fun finishEntering(index: Int, text: String) { val list = _words.value!! - if (text.isNotEmpty() && list.size < SeedPhrase.SeedPhraseLength) { + if (text.isNotEmpty() && list.size < SeedPhrase.SEED_PHRASE_LENGTH) { addWord(index + 1) } _words.value = _words.value @@ -275,10 +264,10 @@ class InputSeedWordsViewModel : CommonViewModel() { fun selectSuggestion(suggestionViewHolderItem: SuggestionViewHolderItem) { onCurrentWordChanges(_focusedIndex.value!!, suggestionViewHolderItem.suggestion + " ") - if (_words.value.orEmpty().size == SeedPhrase.SeedPhraseLength) { - viewModelScope.launch(Dispatchers.IO) { + if (_words.value.orEmpty().size == SeedPhrase.SEED_PHRASE_LENGTH) { + launchOnIo { delay(100) - viewModelScope.launch(Dispatchers.Main) { + launchOnMain { finishEntering() } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringFragment.kt similarity index 77% rename from app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsFragment.kt rename to app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringFragment.kt index 8761f8d02..810e657d3 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringFragment.kt @@ -30,45 +30,45 @@ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.tari.android.wallet.ui.fragment.restore.walletRestoringFromSeedWords +package com.tari.android.wallet.ui.fragment.restore.walletRestoring import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import com.tari.android.wallet.databinding.FragmentWalletRestoringFromSeedWordsBinding -import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.databinding.FragmentWalletRestoringBinding +import com.tari.android.wallet.extension.collectFlow import com.tari.android.wallet.ui.common.CommonFragment -class WalletRestoringFromSeedWordsFragment : - CommonFragment() { +class WalletRestoringFragment : CommonFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = - FragmentWalletRestoringFromSeedWordsBinding.inflate(inflater, container, false).also { ui = it }.root + FragmentWalletRestoringBinding.inflate(inflater, container, false).also { ui = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - changeOnBackPressed(true) - val viewModel: WalletRestoringFromSeedWordsViewModel by viewModels() + val viewModel: WalletRestoringViewModel by viewModels() bindViewModel(viewModel) subscribeUI() viewModel.startRestoring() + + doOnBackPressed { viewModel.showResetFlowDialog() } } private fun subscribeUI() = with(viewModel) { - observe(recoveryState) { processRecoveryState(it) } + collectFlow(recoveryState) { processRecoveryState(it) } } - private fun processRecoveryState(state: WalletRestoringFromSeedWordsViewModel.RecoveryState) { + private fun processRecoveryState(state: WalletRestoringViewModel.RestorationState) { ui.statusLabel.text = state.status ui.progressLabel.text = state.progress } companion object { - fun newInstance() = WalletRestoringFromSeedWordsFragment() + fun newInstance() = WalletRestoringFragment() } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringViewModel.kt new file mode 100644 index 000000000..8f31eccf9 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoring/WalletRestoringViewModel.kt @@ -0,0 +1,180 @@ +package com.tari.android.wallet.ui.fragment.restore.walletRestoring + +import androidx.lifecycle.viewModelScope +import com.tari.android.wallet.R +import com.tari.android.wallet.application.baseNodes.BaseNodesManager +import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto +import com.tari.android.wallet.extension.collectFlow +import com.tari.android.wallet.extension.launchOnIo +import com.tari.android.wallet.extension.launchOnMain +import com.tari.android.wallet.extension.switchToIo +import com.tari.android.wallet.extension.switchToMain +import com.tari.android.wallet.recovery.WalletRestorationState +import com.tari.android.wallet.recovery.WalletRestorationStateHandler +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.common.domain.ResourceManager +import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs +import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.text.DecimalFormat +import javax.inject.Inject + +class WalletRestoringViewModel : CommonViewModel() { + + @Inject + lateinit var baseNodesManager: BaseNodesManager + + @Inject + lateinit var walletRestorationStateHandler: WalletRestorationStateHandler + + private lateinit var baseNodeIterator: Iterator + + private val _recoveryState = MutableStateFlow(RestorationState.ConnectingToBaseNode(resourceManager)) + val recoveryState = _recoveryState.asStateFlow() + + init { + component.inject(this) + } + + fun startRestoring() = launchOnIo { + baseNodeIterator = baseNodesManager.baseNodeList.iterator() + subscribeOnRestorationState() + connectToNextBaseNode() + } + + fun showResetFlowDialog() { + showModularDialog( + HeadModule(resourceManager.getString(R.string.restore_from_seed_words_cancel_dialog_title)), + BodyModule(resourceManager.getString(R.string.restore_from_seed_words_cancel_dialog_description)), + ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Warning) { + viewModelScope.launch(Dispatchers.Main) { + cancelRecovery() + hideDialog() + } + }, + ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) { + hideDialog() + }, + ) + } + + private suspend fun connectToNextBaseNode() = withContext(Dispatchers.IO) { + if (baseNodeIterator.hasNext()) { + val nextBaseNode = baseNodeIterator.next() + logger.i("Trying to start restoring on base node ${nextBaseNode.publicKeyHex}") + startRestoringOnNode(nextBaseNode) + } else { + logger.i("No more base nodes to try") + onError(RestorationError.ConnectionFailed(resourceManager, this@WalletRestoringViewModel::cancelRecovery)) + } + } + + private suspend fun startRestoringOnNode(baseNode: BaseNodeDto) { + try { + val startedSuccessfully = walletManager.startRecovery(baseNode, resourceManager.getString(R.string.restore_wallet_output_message)) + if (!startedSuccessfully) { + connectToNextBaseNode() + onError(RestorationError.ConnectionFailed(resourceManager, this@WalletRestoringViewModel::cancelRecovery)) + } + } catch (e: Throwable) { + onError(RestorationError.RecoveryInternalError(resourceManager, this@WalletRestoringViewModel::cancelRecovery)) + } + } + + private fun subscribeOnRestorationState() { + collectFlow(walletRestorationStateHandler.walletRestorationState) { state -> + launchOnMain { + when (state) { + is WalletRestorationState.ConnectingToBaseNode -> updateState(RestorationState.ConnectingToBaseNode(resourceManager)) + is WalletRestorationState.ConnectedToBaseNode -> updateState(RestorationState.ConnectedToBaseNode(resourceManager)) + is WalletRestorationState.ScanningRoundFailed -> onConnectionFailed(state.retryCount, state.retryLimit) + is WalletRestorationState.ConnectionToBaseNodeFailed -> onConnectionFailed(state.retryCount, state.retryLimit) + is WalletRestorationState.Progress -> updateState( + RestorationState.Recovery(resourceManager, state.currentBlock, state.numberOfBlocks) + ) + + is WalletRestorationState.RecoveryFailed -> onError( + RestorationError.RecoveryInternalError(resourceManager, this@WalletRestoringViewModel::cancelRecovery) + ) + + is WalletRestorationState.Completed -> onSuccessRestoration() + } + } + } + } + + private suspend fun onConnectionFailed(retryCount: Long, retryLimit: Long) = switchToMain { + if (retryCount == retryLimit) { + switchToIo { connectToNextBaseNode() } + } else { + updateState(RestorationState.ConnectionFailed(resourceManager, retryCount, retryLimit)) + } + } + + private fun onError(restorationError: RestorationError) { + if (!baseNodeIterator.hasNext()) { + walletManager.deleteWallet() + showModularDialog(restorationError.args.getModular(resourceManager)) + } + } + + private fun onSuccessRestoration() { + tariSettingsSharedRepository.hasVerifiedSeedWords = true + walletManager.onWalletRestored() + tariNavigator.navigate(Navigation.SplashScreen(clearTop = false)) + } + + private fun updateState(recoveryState: RestorationState) = _recoveryState.update { recoveryState } + + private fun cancelRecovery() { + walletManager.deleteWallet() + tariNavigator.navigate(Navigation.SplashScreen()) + } + + sealed class RestorationError(title: String, message: String, dismissAction: () -> Unit) { + + val args = SimpleDialogArgs(title = title, description = message, onClose = dismissAction) + + class ConnectionFailed(resourceManager: ResourceManager, dismissAction: () -> Unit) : RestorationError( + title = resourceManager.getString(R.string.restore_from_seed_words_overlay_error_title), + message = resourceManager.getString(R.string.restore_from_seed_words_overlay_error_description_connection_failed), + dismissAction = dismissAction, + ) + + class RecoveryInternalError(resourceManager: ResourceManager, dismissAction: () -> Unit) : RestorationError( + title = resourceManager.getString(R.string.restore_from_seed_words_overlay_error_title), + message = resourceManager.getString(R.string.restore_from_seed_words_overlay_error_description_internal_error), + dismissAction = dismissAction, + ) + } + + sealed class RestorationState(val status: String = "", val progress: String = "") { + + class ConnectingToBaseNode(resourceManager: ResourceManager) : RestorationState( + status = resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connecting) + ) + + class ConnectionFailed(resourceManager: ResourceManager, attempt: Long, maxAttempts: Long) : RestorationState( + status = resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connecting), + progress = resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connection_failed, attempt + 1, maxAttempts + 1), + ) + + class ConnectedToBaseNode(resourceManager: ResourceManager) : RestorationState( + status = resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connected) + ) + + class Recovery(resourceManager: ResourceManager, currentBlocks: Long, allBlocks: Long) : RestorationState( + status = resourceManager.getString(R.string.restore_from_seed_words_overlay_status_progress), + progress = DecimalFormat("#.##").format((currentBlocks.toDouble() / allBlocks) * 100) + "%", + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt deleted file mode 100644 index 262930aba..000000000 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt +++ /dev/null @@ -1,160 +0,0 @@ -package com.tari.android.wallet.ui.fragment.restore.walletRestoringFromSeedWords - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.tari.android.wallet.R -import com.tari.android.wallet.application.baseNodes.BaseNodesManager -import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto -import com.tari.android.wallet.event.EventBus -import com.tari.android.wallet.extension.addTo -import com.tari.android.wallet.ffi.FFIPublicKey -import com.tari.android.wallet.ffi.FFIWallet -import com.tari.android.wallet.ffi.HexString -import com.tari.android.wallet.model.recovery.WalletRestorationResult -import com.tari.android.wallet.service.seedPhrase.SeedPhraseRepository -import com.tari.android.wallet.service.service.WalletServiceLauncher -import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.common.domain.ResourceManager -import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs -import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.text.DecimalFormat -import javax.inject.Inject - -class WalletRestoringFromSeedWordsViewModel : CommonViewModel() { - - - @Inject - lateinit var seedPhraseRepository: SeedPhraseRepository - - @Inject - lateinit var walletServiceLauncher: WalletServiceLauncher - - @Inject - lateinit var baseNodesManager: BaseNodesManager - - private lateinit var baseNodeIterator: Iterator - - private val _recoveryState = MutableLiveData(RecoveryState.ConnectingToBaseNode(resourceManager)) - val recoveryState: LiveData = _recoveryState - - init { - component.inject(this) - } - - fun startRestoring() = viewModelScope.launch(Dispatchers.IO) { - baseNodeIterator = baseNodesManager.baseNodeList.iterator() - tryNextBaseNode() - } - - private fun tryNextBaseNode() = viewModelScope.launch(Dispatchers.IO) { - logger.i("set next base node ${baseNodeIterator.hasNext()}") - if (baseNodeIterator.hasNext()) { - startRestoringOnNode(baseNodeIterator.next()) - } else { - onError(RestorationError.ConnectionFailed(resourceManager, this@WalletRestoringFromSeedWordsViewModel::onErrorClosed)) - } - } - - private fun startRestoringOnNode(baseNode: BaseNodeDto) { - try { - val baseNodeFFI = FFIPublicKey(HexString(baseNode.publicKeyHex)) - val result = FFIWallet.instance?.startRecovery(baseNodeFFI, resourceManager.getString(R.string.restore_wallet_output_message)) - if (result == true) { - subscribeOnRestorationState() - return - } else { - tryNextBaseNode() - } - onError(RestorationError.ConnectionFailed(resourceManager, this@WalletRestoringFromSeedWordsViewModel::onErrorClosed)) - } catch (e: Throwable) { - onError(RestorationError.RecoveryInternalError(resourceManager, this@WalletRestoringFromSeedWordsViewModel::onErrorClosed)) - } - } - - private fun subscribeOnRestorationState() { - EventBus.walletRestorationState.publishSubject.subscribe { - logger.i(it.toString()) - when (it) { - is WalletRestorationResult.ConnectingToBaseNode -> onProgress(RecoveryState.ConnectingToBaseNode(resourceManager)) - is WalletRestorationResult.ConnectedToBaseNode -> onProgress(RecoveryState.ConnectedToBaseNode(resourceManager)) - is WalletRestorationResult.ScanningRoundFailed -> onConnectionFailed(it.retryCount, it.retryLimit) - is WalletRestorationResult.ConnectionToBaseNodeFailed -> onConnectionFailed(it.retryCount, it.retryLimit) - is WalletRestorationResult.Progress -> onProgress(RecoveryState.Recovery(resourceManager, it.currentBlock, it.numberOfBlocks)) - is WalletRestorationResult.RecoveryFailed -> { - logger.i("recovery failed ${baseNodeIterator.hasNext()}") - if (!baseNodeIterator.hasNext()) { - onError(RestorationError.RecoveryInternalError(resourceManager, this@WalletRestoringFromSeedWordsViewModel::onErrorClosed)) - } - } - - is WalletRestorationResult.Completed -> onSuccessRestoration() - } - }.addTo(compositeDisposable) - } - - private fun onConnectionFailed(retryCount: Long, retryLimit: Long) { - if (retryCount == retryLimit) { - compositeDisposable.dispose() - compositeDisposable = CompositeDisposable() - tryNextBaseNode() - } else { - onProgress(RecoveryState.ConnectionFailed(resourceManager, retryCount, retryLimit)) - } - } - - private fun onError(restorationError: RestorationError) { - walletServiceLauncher.stopAndDelete() - showModularDialog(restorationError.args.getModular(resourceManager)) - } - - private fun onSuccessRestoration() { - tariSettingsSharedRepository.hasVerifiedSeedWords = true - navigation.postValue(Navigation.WalletRestoringFromSeedWordsNavigation.OnRestoreCompleted) - } - - private fun onProgress(recoveryState: RecoveryState) = _recoveryState.postValue(recoveryState) - - private fun onErrorClosed() = navigation.postValue(Navigation.WalletRestoringFromSeedWordsNavigation.OnRestoreFailed) - - sealed class RestorationError(title: String, message: String, dismissAction: () -> Unit) { - - val args = SimpleDialogArgs(title = title, description = message, onClose = dismissAction) - - class ConnectionFailed(resourceManager: ResourceManager, dismissAction: () -> Unit) : RestorationError( - resourceManager.getString(R.string.restore_from_seed_words_overlay_error_title), - resourceManager.getString(R.string.restore_from_seed_words_overlay_error_description_connection_failed), - dismissAction - ) - - class RecoveryInternalError(resourceManager: ResourceManager, dismissAction: () -> Unit) : RestorationError( - resourceManager.getString(R.string.restore_from_seed_words_overlay_error_title), - resourceManager.getString(R.string.restore_from_seed_words_overlay_error_description_internal_error), - dismissAction - ) - } - - sealed class RecoveryState(val status: String = "", val progress: String = "") { - - class ConnectingToBaseNode(resourceManager: ResourceManager) : RecoveryState( - resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connecting) - ) - - class ConnectionFailed(resourceManager: ResourceManager, attempt: Long, maxAttempts: Long) : RecoveryState( - resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connecting), - resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connection_failed, attempt + 1, maxAttempts + 1) - ) - - class ConnectedToBaseNode(resourceManager: ResourceManager) : RecoveryState( - resourceManager.getString(R.string.restore_from_seed_words_overlay_status_connected) - ) - - class Recovery(resourceManager: ResourceManager, currentBlocks: Long, allBlocks: Long) : RecoveryState( - resourceManager.getString(R.string.restore_from_seed_words_overlay_status_progress), - DecimalFormat("#.##").format((currentBlocks.toDouble() / allBlocks) * 100) + "%" - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt index 98c729e41..8eefa0d58 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt @@ -81,7 +81,7 @@ import com.tari.android.wallet.ui.fragment.send.amountView.AmountStyle import com.tari.android.wallet.ui.fragment.send.common.TransactionData import com.tari.android.wallet.util.Constants import com.tari.android.wallet.util.DebugConfig -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.util.addressFirstEmojis import com.tari.android.wallet.util.addressLastEmojis import com.tari.android.wallet.util.addressPrefixEmojis @@ -192,7 +192,7 @@ class AddAmountFragment : CommonFragment private fun onSlideAnimationEnd() { hideKeyboard() - if (EventBus.networkConnectionState.publishSubject.value != NetworkConnectionState.CONNECTED) { + if (!viewModel.isNetworkConnectionAvailable()) { ui.rootView.postDelayed(Constants.UI.keyboardHideWaitMs) { restoreSlider() ui.noteEditText.isEnabled = true diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteViewModel.kt index e26cc94ef..7efe07d8b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteViewModel.kt @@ -1,12 +1,17 @@ package com.tari.android.wallet.ui.fragment.send.addNote import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.network.NetworkConnectionStateHandler import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.send.common.TransactionData +import javax.inject.Inject class AddNoteViewModel : CommonViewModel() { + @Inject + lateinit var networkConnection: NetworkConnectionStateHandler + init { component.inject(this) } @@ -18,4 +23,6 @@ class AddNoteViewModel : CommonViewModel() { fun continueToFinalizeSendTx(newData: TransactionData) { tariNavigator.navigate(Navigation.AddAmountNavigation.ContinueToFinalizing(newData)) } + + fun isNetworkConnectionAvailable(): Boolean = networkConnection.isNetworkConnected() } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/finalize/FinalizeSendTxViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/finalize/FinalizeSendTxViewModel.kt index fcc411f6c..db1cebeaa 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/finalize/FinalizeSendTxViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/finalize/FinalizeSendTxViewModel.kt @@ -8,15 +8,16 @@ import com.tari.android.wallet.R.string.finalize_send_tx_sending_step_2_desc_lin import com.tari.android.wallet.R.string.finalize_send_tx_sending_step_2_desc_line_2 import com.tari.android.wallet.R.string.finalize_send_tx_sending_step_3_desc_line_1 import com.tari.android.wallet.R.string.finalize_send_tx_sending_step_3_desc_line_2 -import com.tari.android.wallet.event.Event +import com.tari.android.wallet.application.walletManager.WalletManager import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.extension.collectFlow import com.tari.android.wallet.extension.launchOnIo import com.tari.android.wallet.extension.launchOnMain import com.tari.android.wallet.model.TariContact import com.tari.android.wallet.model.TransactionSendStatus import com.tari.android.wallet.model.TxId import com.tari.android.wallet.model.WalletError -import com.tari.android.wallet.network.NetworkConnectionState +import com.tari.android.wallet.network.NetworkConnectionStateHandler import com.tari.android.wallet.tor.TorBootstrapStatus import com.tari.android.wallet.tor.TorProxyState import com.tari.android.wallet.tor.TorProxyStateHandler @@ -33,6 +34,9 @@ class FinalizeSendTxViewModel : CommonViewModel() { @Inject lateinit var torProxyStateHandler: TorProxyStateHandler + @Inject + lateinit var networkConnection: NetworkConnectionStateHandler + lateinit var transactionData: TransactionData val steps: MutableLiveData> = MutableLiveData>() @@ -118,8 +122,7 @@ class FinalizeSendTxViewModel : CommonViewModel() { val secondsElapsed = Seconds.secondsBetween(connectionCheckStartTime, DateTime.now()).seconds if (secondsElapsed >= CONNECTION_TIMEOUT_SEC) { - val networkConnectionState = EventBus.networkConnectionState.publishSubject.value - if (networkConnectionState != NetworkConnectionState.CONNECTED) { + if (!networkConnection.isNetworkConnected()) { // internet connection problem txFailureReason.value = TxFailureReason.NETWORK_CONNECTION_ERROR } else { @@ -140,10 +143,9 @@ class FinalizeSendTxViewModel : CommonViewModel() { } private fun checkConnectionStatus() { - val networkConnectionState = EventBus.networkConnectionState.publishSubject.value val torProxyState = torProxyStateHandler.torProxyState.value // check internet connection - if (networkConnectionState != NetworkConnectionState.CONNECTED) { + if (!networkConnection.isNetworkConnected()) { // either not connected or Tor proxy is not running txFailureReason.value = TxFailureReason.NETWORK_CONNECTION_ERROR isCompleted = true @@ -205,15 +207,16 @@ class FinalizeSendTxViewModel : CommonViewModel() { ) { init { - subscribeToEventBus() + collectFlow(walletManager.walletEvent) { event -> + when (event) { + is WalletManager.WalletEvent.Tx.DirectSendResult -> onDirectSendResult(event.txId, event.status) + else -> Unit + } + } } override fun execute() = Unit - private fun subscribeToEventBus() { - EventBus.subscribe(this) { event -> onDirectSendResult(event.txId, event.status) } - } - private fun onDirectSendResult(txId: TxId, status: TransactionSendStatus) { if (sentTxId.value != txId) { return diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/requestTari/RequestTariFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/requestTari/RequestTariFragment.kt index f7d7a709b..039ebe3e4 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/requestTari/RequestTariFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/requestTari/RequestTariFragment.kt @@ -81,7 +81,7 @@ class RequestTariFragment : CommonFragment { + if (!settingsRepository.walletAddressExists()) return emptyList() // Return empty list if this method called after wallet is deleted + val versionText = TariVersionModel(networkRepository).versionInfo val alias = settingsRepository.firstName.orEmpty() + " " + settingsRepository.lastName.orEmpty() @@ -239,10 +241,10 @@ class AllSettingsViewModel : CommonViewModel() { SettingsRowViewHolderItem(resourceManager.getString(all_settings_select_network), vector_all_settings_select_network_icon) { navigation.postValue(AllSettingsNavigation.ToNetworkSelection) }, - DividerViewHolderItem(), + DividerViewHolderItem().takeIf { DebugConfig.selectBaseNodeEnabled }, SettingsRowViewHolderItem(resourceManager.getString(all_settings_select_base_node), vector_all_settings_select_base_node_icon) { navigation.postValue(AllSettingsNavigation.ToBaseNodeSelection) - }, + }.takeIf { DebugConfig.selectBaseNodeEnabled }, DividerViewHolderItem(), SettingsRowViewHolderItem( title = resourceManager.getString(all_settings_delete_wallet), diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backgroundService/BackgroundServiceSettingsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backgroundService/BackgroundServiceSettingsViewModel.kt index 053e5aa11..b52c40c43 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backgroundService/BackgroundServiceSettingsViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backgroundService/BackgroundServiceSettingsViewModel.kt @@ -44,6 +44,6 @@ class BackgroundServiceSettingsViewModel : CommonViewModel() { tariSettingsSharedRepository.backgroundServiceTurnedOn = isTurnedOn _switchState.value = TariLoadingSwitchState(isTurnedOn, false) hideDialog() - serviceLauncher.startIfExist() + serviceLauncher.startIfWalletExists() } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/option/BackupOptionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/option/BackupOptionViewModel.kt index 463da942c..539ed6604 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/option/BackupOptionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/option/BackupOptionViewModel.kt @@ -21,7 +21,7 @@ import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptionDto -import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOptions +import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupOption import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.joda.time.DateTime @@ -57,12 +57,12 @@ class BackupOptionViewModel : CommonViewModel() { val title: Int get() = when (option.value!!.type) { - BackupOptions.Google -> R.string.back_up_wallet_google_title - BackupOptions.Local -> R.string.back_up_wallet_local_file_title - BackupOptions.Dropbox -> R.string.back_up_wallet_dropbox_backup_title + BackupOption.Google -> R.string.back_up_wallet_google_title + BackupOption.Local -> R.string.back_up_wallet_local_file_title + BackupOption.Dropbox -> R.string.back_up_wallet_dropbox_backup_title } - fun setup(option: BackupOptions) { + fun setup(option: BackupOption) { _option.value = backupSettingsRepository.getOptionList.first { it.type == option } _switchChecked.value = _option.value!!.isEnable onBackupStateChanged(EventBus.backupState.publishSubject.value?.backupsStates?.get(option)) @@ -73,7 +73,7 @@ class BackupOptionViewModel : CommonViewModel() { val currentOption = _option.value!!.type try { if (backupManager.onSetupActivityResult(requestCode, resultCode, data)) { - backupSettingsRepository.getOptionDto(currentOption)?.copy(isEnable = true)?.let { backupSettingsRepository.updateOption(it) } + backupSettingsRepository.getOptionDto(currentOption).copy(isEnable = true).let { backupSettingsRepository.updateOption(it) } EventBus.backupState.publishSubject .filter { it.backupsStates[currentOption] is BackupState.BackupUpToDate || it.backupsStates[currentOption] is BackupState.BackupFailed @@ -89,7 +89,7 @@ class BackupOptionViewModel : CommonViewModel() { } } - private fun turnOff(backupOption: BackupOptions, throwable: Throwable?) { + private fun turnOff(backupOption: BackupOption, throwable: Throwable?) { logger.i("Backup storage setup failed: $throwable") backupManager.turnOff(backupOption) _inProgress.postValue(false) @@ -187,7 +187,7 @@ class BackupOptionViewModel : CommonViewModel() { val currentState = backupSettingsRepository.getOptionDto(_option.value!!.type) _inProgress.postValue(false) _switchChecked.postValue(true) - updateLastSuccessfulBackupDate(currentState?.lastSuccessDate?.date) + updateLastSuccessfulBackupDate(currentState.lastSuccessDate?.date) } private fun handleInProgressState() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/changeSecurePassword/ChangeSecurePasswordFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/changeSecurePassword/ChangeSecurePasswordFragment.kt index 85e4f112b..fd8d34820 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/changeSecurePassword/ChangeSecurePasswordFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/changeSecurePassword/ChangeSecurePasswordFragment.kt @@ -302,7 +302,7 @@ class ChangeSecurePasswordFragment : CommonFragment() { @@ -91,6 +92,8 @@ class WriteDownSeedPhraseFragment : CommonFragment>() val baseNodeList: LiveData> = _baseNodeList @@ -45,18 +45,19 @@ class ChangeBaseNodeViewModel : CommonViewModel() { init { component.inject(this) - EventBus.baseNodeState.subscribe(this) { loadList() } + collectFlow(baseNodeStateHandler.baseNodeState) { loadList() } loadList() } fun selectBaseNode(baseNodeDto: BaseNodeDto) { baseNodesManager.setBaseNode(baseNodeDto) + walletManager.syncBaseNode() backPressed.postValue(Unit) } fun showQrCode(baseNodeDto: BaseNodeDto) { - val data = deeplinkHandler.getDeeplink(baseNodeDto.toDeeplink()) + val data = deeplinkManager.getDeeplinkString(baseNodeDto.toDeeplink()) showModularDialog( ModularDialogArgs( DialogArgs(true, canceledOnTouchOutside = true), listOf( @@ -76,9 +77,9 @@ class ChangeBaseNodeViewModel : CommonViewModel() { } private fun loadList() { - val currentBaseNode = baseNodeSharedRepository.currentBaseNode + val currentBaseNode = baseNodesManager.currentBaseNode val items = mutableListOf() - items.addAll(baseNodeSharedRepository.userBaseNodes.map { BaseNodeViewHolderItem(it, currentBaseNode, this::deleteBaseNode) }) + items.addAll(baseNodesManager.userBaseNodes.map { BaseNodeViewHolderItem(it, currentBaseNode, this::deleteBaseNode) }) items.addAll(baseNodesManager.baseNodeList.map { BaseNodeViewHolderItem(it, currentBaseNode, this::deleteBaseNode) }) _baseNodeList.postValue(items) } @@ -136,6 +137,7 @@ class ChangeBaseNodeViewModel : CommonViewModel() { baseNodesManager.addUserBaseNode(baseNode) baseNodesManager.setBaseNode(baseNode) + walletManager.syncBaseNode() hideDialog() loadList() } else { @@ -148,5 +150,4 @@ class ChangeBaseNodeViewModel : CommonViewModel() { ) } } - } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/deleteWallet/DeleteWalletFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/deleteWallet/DeleteWalletFragment.kt index 35bb524f2..49dcc9c59 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/deleteWallet/DeleteWalletFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/deleteWallet/DeleteWalletFragment.kt @@ -38,16 +38,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import com.tari.android.wallet.databinding.FragmentDeleteWalletBinding import com.tari.android.wallet.extension.observe import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.extension.ThrottleClick import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class DeleteWalletFragment : CommonFragment() { @@ -71,21 +67,13 @@ class DeleteWalletFragment : CommonFragment() - - @Inject - lateinit var walletServiceLauncher: WalletServiceLauncher + val showProgress = SingleLiveEvent() + val goToSplash = SingleLiveEvent() init { component.inject(this) @@ -26,10 +24,20 @@ class DeleteWalletViewModel : CommonViewModel() { HeadModule(resourceManager.getString(R.string.delete_wallet_confirmation_title)), BodyModule(resourceManager.getString(R.string.delete_wallet_confirmation_description)), ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Warning) { - deleteWallet.postValue(Unit) + onDeleteWalletClicked() hideDialog() }, ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) ) } + + private fun onDeleteWalletClicked() { + showProgress.postValue(Unit) + launchOnIo { + walletManager.deleteWallet() + launchOnMain { + goToSplash.postValue(Unit) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/LogFilesManager.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/LogFilesManager.kt index e8c840ae5..d137a04a6 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/LogFilesManager.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/LogFilesManager.kt @@ -5,7 +5,7 @@ import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import kotlinx.coroutines.launch import javax.inject.Inject @@ -22,7 +22,7 @@ class LogFilesManager : CommonViewModel() { } private fun manage() = viewModelScope.launch { - val files = WalletUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() + val files = WalletFileUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() if (files.size > MAX_FILES) { files.sortedByDescending { it.lastModified() }.drop(10).forEach { fileToDelete -> runCatching { fileToDelete.delete() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logFiles/LogFilesViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logFiles/LogFilesViewModel.kt index 982e24aa8..274cd7dce 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logFiles/LogFilesViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logFiles/LogFilesViewModel.kt @@ -7,7 +7,7 @@ import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.common.recyclerView.items.DividerViewHolderItem import com.tari.android.wallet.ui.fragment.settings.logs.logFiles.adapter.LogFileViewHolderItem -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import java.io.File import java.text.DecimalFormat import javax.inject.Inject @@ -26,7 +26,7 @@ class LogFilesViewModel : CommonViewModel() { init { component.inject(this) - val files = WalletUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() + val files = WalletFileUtil.getLogFilesFromDirectory(walletConfig.getWalletLogFilesDirPath()).toMutableList() val wholeList = files.map { listOf(LogFileViewHolderItem(getFileName(it), it) { goNext.postValue(it.file) }, DividerViewHolderItem()) } .flatten() .toMutableList() @@ -35,7 +35,7 @@ class LogFilesViewModel : CommonViewModel() { private fun getFileName(file: File): String = file.name + " - " + getReadableFileSize(file.length()) - private fun getReadableFileSize(size: Long): String? { + private fun getReadableFileSize(size: Long): String { if (size <= 0) return "0" val units = arrayOf("B", "KB", "MB", "GB", "TB") val digitGroups = (log10(size.toDouble()) / log10(bytesInKilo)).toInt() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt index cd946028d..a744f76bc 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt @@ -2,20 +2,20 @@ package com.tari.android.wallet.ui.fragment.settings.networkSelection import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R +import com.tari.android.wallet.application.walletManager.doOnWalletNotReady import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.network.TariNetwork import com.tari.android.wallet.di.DiContainer import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.extension.launchOnIo import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.dialog.confirm.ConfirmDialogArgs import com.tari.android.wallet.ui.fragment.settings.networkSelection.networkItem.NetworkViewHolderItem -import com.tari.android.wallet.util.WalletUtil -import kotlinx.coroutines.launch +import com.tari.android.wallet.application.walletManager.WalletFileUtil import javax.inject.Inject class NetworkSelectionViewModel : CommonViewModel() { @@ -49,7 +49,7 @@ class NetworkSelectionViewModel : CommonViewModel() { return } - if (WalletUtil.walletExists(walletConfig)) { + if (WalletFileUtil.walletExists(walletConfig)) { showModularDialog( ConfirmDialogArgs( title = resourceManager.getString(R.string.all_settings_select_network_confirm_title), @@ -66,8 +66,8 @@ class NetworkSelectionViewModel : CommonViewModel() { networkRepository.currentNetwork = newNetwork loadData() - viewModelScope.launch { - walletStateHandler.doOnWalletNotReady { + launchOnIo { + walletManager.doOnWalletNotReady { EventBus.clear() DiContainer.reInitContainer() _recreate.postValue(Unit) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/themeSelector/ThemeSelectorViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/themeSelector/ThemeSelectorViewModel.kt index 6c48ef770..8098d3efd 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/themeSelector/ThemeSelectorViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/themeSelector/ThemeSelectorViewModel.kt @@ -1,13 +1,18 @@ package com.tari.android.wallet.ui.fragment.settings.themeSelector import androidx.lifecycle.MutableLiveData -import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.extension.collectFlow +import com.tari.android.wallet.service.baseNode.BaseNodeStateHandler import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.fragment.settings.themeSelector.adapter.ThemeViewHolderItem +import javax.inject.Inject class ThemeSelectorViewModel : CommonViewModel() { + @Inject + lateinit var baseNodeStateHandler: BaseNodeStateHandler + val themes: MutableLiveData> = MutableLiveData() val newTheme = SingleLiveEvent() @@ -15,7 +20,7 @@ class ThemeSelectorViewModel : CommonViewModel() { init { component.inject(this) - EventBus.baseNodeState.subscribe(this) { loadList() } + collectFlow(baseNodeStateHandler.baseNodeState) { loadList() } loadList() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionViewModel.kt index c2cf2bc2a..4c1448105 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionViewModel.kt @@ -3,9 +3,8 @@ package com.tari.android.wallet.ui.fragment.settings.torBridges import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.tari.android.wallet.R -import com.tari.android.wallet.application.baseNodes.BaseNodesManager import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfigurationList import com.tari.android.wallet.data.sharedPrefs.tor.TorPrefRepository import com.tari.android.wallet.extension.collectFlow @@ -15,9 +14,9 @@ import com.tari.android.wallet.tor.TorProxyManager import com.tari.android.wallet.tor.TorProxyState import com.tari.android.wallet.tor.TorProxyStateHandler import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs import com.tari.android.wallet.ui.dialog.modular.DialogArgs import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs +import com.tari.android.wallet.ui.dialog.modular.SimpleDialogArgs import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle @@ -25,6 +24,7 @@ import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.send.shareQr.ShareQrCodeModule import com.tari.android.wallet.ui.fragment.settings.torBridges.torItem.TorBridgeViewHolderItem +import com.tari.android.wallet.util.DebugConfig import kotlinx.coroutines.Job import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -39,10 +39,7 @@ class TorBridgesSelectionViewModel : CommonViewModel() { lateinit var torProxyManager: TorProxyManager @Inject - lateinit var baseNodesManager: BaseNodesManager - - @Inject - lateinit var deeplinkHandler: DeeplinkHandler + lateinit var deeplinkManager: DeeplinkManager @Inject lateinit var torProxyStateHandler: TorProxyStateHandler @@ -102,7 +99,7 @@ class TorBridgesSelectionViewModel : CommonViewModel() { fun showBridgeQrCode(torBridgeItem: TorBridgeViewHolderItem) { if (torBridgeItem !is TorBridgeViewHolderItem.Bridge) return - val data = deeplinkHandler.getDeeplink(DeepLink.TorBridges(listOf(torBridgeItem.bridgeConfiguration))) + val data = deeplinkManager.getDeeplinkString(DeepLink.TorBridges(listOf(torBridgeItem.bridgeConfiguration))) showModularDialog( ModularDialogArgs( DialogArgs(true, canceledOnTouchOutside = true), listOf( @@ -139,7 +136,9 @@ class TorBridgesSelectionViewModel : CommonViewModel() { torProxyManager.shutdown() subscribeToTorState() torProxyManager.run() - baseNodesManager.startSync() + if (DebugConfig.selectBaseNodeEnabled) { + walletManager.syncBaseNode() + } } private fun subscribeToTorState() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt index 6fde6b0d7..ab4fcf8b4 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt @@ -8,12 +8,14 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import com.tari.android.wallet.R +import com.tari.android.wallet.application.deeplinks.DeepLink import com.tari.android.wallet.databinding.FragmentCustomTorBridgesBinding import com.tari.android.wallet.extension.observe import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.component.tari.toolbar.TariToolbarActionArg +import com.tari.android.wallet.ui.extension.parcelable import com.tari.android.wallet.ui.extension.setOnThrottledClickListener -import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerActivity import com.tari.android.wallet.ui.fragment.qr.QrScannerSource class CustomTorBridgesFragment : CommonFragment() { @@ -33,7 +35,7 @@ class CustomTorBridgesFragment : CommonFragment(QrScannerActivity.EXTRA_DEEPLINK) ?: return + viewModel.handleQrCode(qrDeepLink) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt index fcfb047ab..fea386a64 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt @@ -2,7 +2,7 @@ package com.tari.android.wallet.ui.fragment.settings.torBridges.customBridges import com.tari.android.wallet.R import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfiguration import com.tari.android.wallet.data.sharedPrefs.tor.TorPrefRepository import com.tari.android.wallet.ui.common.CommonViewModel @@ -16,7 +16,7 @@ class CustomTorBridgesViewModel : CommonViewModel() { lateinit var torSharedRepository: TorPrefRepository @Inject - lateinit var deeplinkHandler: DeeplinkHandler + lateinit var deeplinkManager: DeeplinkManager var text = SingleLiveEvent() @@ -65,10 +65,9 @@ class CustomTorBridgesViewModel : CommonViewModel() { backPressed.postValue(Unit) } - fun handleQrCode(input: String) { - val deeplink = deeplinkHandler.handle(input) + fun handleQrCode(deeplink: DeepLink) { if (deeplink is DeepLink.TorBridges) { - val text = deeplinkHandler.getDeeplink(deeplink) + val text = deeplinkManager.getDeeplinkString(deeplink) this.text.postValue(text) } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt index b9b2ed5f6..c673e0bec 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt @@ -38,7 +38,9 @@ import android.os.Bundle import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.coroutineScope -import com.tari.android.wallet.application.walletManager.WalletStateHandler +import com.tari.android.wallet.application.walletManager.WalletFileUtil +import com.tari.android.wallet.application.walletManager.WalletManager +import com.tari.android.wallet.application.walletManager.doOnWalletNotReady import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkPrefRepository @@ -49,7 +51,6 @@ import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.fragment.auth.AuthActivity import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowActivity -import com.tari.android.wallet.util.WalletUtil import kotlinx.coroutines.launch import javax.inject.Inject @@ -76,7 +77,7 @@ class SplashActivity : AppCompatActivity() { lateinit var walletServiceLauncher: WalletServiceLauncher @Inject - lateinit var walletStateHandler: WalletStateHandler + lateinit var walletManager: WalletManager override fun onCreate(savedInstanceState: Bundle?) { appComponent.inject(this) @@ -85,31 +86,34 @@ class SplashActivity : AppCompatActivity() { onBackPressedDispatcher.addCallback { } if (sharedPrefsRepository.checkIfIsDataCleared()) { - walletServiceLauncher.stopAndDelete() + walletManager.deleteWallet() } if (!networkRepository.isCurrentNetworkSupported()) { changeNetwork() } - val exists = WalletUtil.walletExists(walletConfig) && sharedPrefsRepository.onboardingAuthSetupCompleted - if (WalletUtil.walletExists(walletConfig) && !sharedPrefsRepository.onboardingAuthSetupCompleted) { + val exists = WalletFileUtil.walletExists(walletConfig) && sharedPrefsRepository.onboardingAuthSetupCompleted + if (WalletFileUtil.walletExists(walletConfig) && !sharedPrefsRepository.onboardingAuthSetupCompleted) { // in cases interrupted restoration - WalletUtil.clearWalletFiles(walletConfig.getWalletFilesDirPath()) + walletManager.deleteWallet() sharedPrefsRepository.clear() } - if (securityPrefRepository.pinCode == null) { - launchActivity(OnboardingFlowActivity::class.java) - return - } - launchActivity(if (exists) AuthActivity::class.java else OnboardingFlowActivity::class.java) + + launchActivity( + if (securityPrefRepository.pinCode == null || !exists) { + OnboardingFlowActivity::class.java + } else { + AuthActivity::class.java + } + ) } private fun changeNetwork() { networkRepository.setDefaultNetworkAsCurrent() lifecycle.coroutineScope.launch { - walletStateHandler.doOnWalletNotReady { + walletManager.doOnWalletNotReady { EventBus.clear() DiContainer.reInitContainer() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt index d9f4e93d8..b2c6e1d15 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt @@ -44,7 +44,8 @@ import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import com.tari.android.wallet.R -import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel +import com.tari.android.wallet.application.deeplinks.DeepLink +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.databinding.FragmentHomeBinding import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.observe @@ -55,14 +56,14 @@ import com.tari.android.wallet.ui.common.recyclerView.AdapterFactory import com.tari.android.wallet.ui.common.recyclerView.CommonAdapter import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.component.networkStateIndicator.ConnectionIndicatorViewModel +import com.tari.android.wallet.ui.extension.parcelable import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.fragment.home.navigation.Navigation -import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerActivity import com.tari.android.wallet.ui.fragment.qr.QrScannerSource import com.tari.android.wallet.ui.fragment.tx.adapter.TxListHomeViewHolder import com.tari.android.wallet.ui.fragment.tx.questionMark.QuestionMarkViewModel import com.tari.android.wallet.ui.fragment.tx.ui.balanceController.BalanceViewController -import com.tari.android.wallet.util.WalletUtil class HomeFragment : CommonFragment() { @@ -76,8 +77,6 @@ class HomeFragment : CommonFragment( // This listener is used only to animate the visibility of the scroll depth gradient view. private lateinit var balanceViewController: BalanceViewController - private val deeplinkViewModel: DeeplinkViewModel by viewModels() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = FragmentHomeBinding.inflate(inflater, container, false).also { ui = it }.root @@ -87,8 +86,6 @@ class HomeFragment : CommonFragment( val viewModel: HomeFragmentViewModel by viewModels() bindViewModel(viewModel) - subscribeVM(deeplinkViewModel) - viewModel.checkPermission() setupUI() subscribeToViewModel() @@ -132,14 +129,13 @@ class HomeFragment : CommonFragment( override fun onDestroyView() { EventBus.unsubscribe(this) - EventBus.networkConnectionState.unsubscribe(this) super.onDestroyView() } @SuppressLint("ClickableViewAccessibility") private fun setupUI() = with(ui) { viewAllTxsButton.setOnClickListener { viewModel.navigation.postValue(Navigation.TxListNavigation.HomeTransactionHistory) } - qrCodeButton.setOnClickListener { QRScannerActivity.startScanner(requireActivity(), QrScannerSource.Home) } + qrCodeButton.setOnClickListener { QrScannerActivity.startScanner(this@HomeFragment, QrScannerSource.Home) } transactionsRecyclerView.adapter = adapter adapter.setClickListener(CommonAdapter.ItemClickListener { viewModel.processItemClick(it) }) transactionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) @@ -156,16 +152,16 @@ class HomeFragment : CommonFragment( @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == QRScannerActivity.REQUEST_QR_SCANNER && resultCode == Activity.RESULT_OK && data != null) { - val qrData = data.getStringExtra(QRScannerActivity.EXTRA_QR_DATA) ?: return - deeplinkViewModel.tryToHandle(qrData) + if (requestCode == QrScannerActivity.REQUEST_QR_SCANNER && resultCode == Activity.RESULT_OK && data != null) { + val qrDeepLink = data.parcelable(QrScannerActivity.EXTRA_DEEPLINK) ?: return + viewModel.handleDeeplink(requireActivity(), qrDeepLink) } } private fun updateBalanceInfoUI(restart: Boolean) { val balanceInfo = viewModel.balanceInfo.value!! - val availableBalance = WalletUtil.balanceFormatter.format(balanceInfo.availableBalance.tariValue) + val availableBalance = WalletFileUtil.balanceFormatter.format(balanceInfo.availableBalance.tariValue) ui.availableBalance.text = availableBalance if (restart) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt index 56003a682..9bcb8bba3 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt @@ -1,20 +1,23 @@ package com.tari.android.wallet.ui.fragment.tx +import android.app.Activity import android.os.Build import android.text.SpannableString import android.text.Spanned import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R import com.tari.android.wallet.R.string.error_no_connection_description import com.tari.android.wallet.R.string.error_no_connection_title import com.tari.android.wallet.R.string.error_node_unreachable_description import com.tari.android.wallet.R.string.error_node_unreachable_title +import com.tari.android.wallet.application.deeplinks.DeepLink +import com.tari.android.wallet.application.deeplinks.DeeplinkManager import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager.StagedSecurityEffect +import com.tari.android.wallet.application.walletManager.WalletManager.WalletEvent import com.tari.android.wallet.data.sharedPrefs.CorePrefRepository import com.tari.android.wallet.data.sharedPrefs.securityStages.WalletSecurityStage import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository @@ -46,8 +49,6 @@ import com.tari.android.wallet.ui.fragment.tx.adapter.TransactionItem import com.tari.android.wallet.util.EmojiId import com.tari.android.wallet.util.extractEmojis import com.tari.android.wallet.util.shortString -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import yat.android.ui.extension.HtmlHelper import javax.inject.Inject @@ -69,6 +70,9 @@ class HomeFragmentViewModel : CommonViewModel() { @Inject lateinit var stagedWalletSecurityManager: StagedWalletSecurityManager + @Inject + lateinit var deeplinkManager: DeeplinkManager + private val _balanceInfo = MutableLiveData() val balanceInfo: LiveData = _balanceInfo @@ -95,6 +99,8 @@ class HomeFragmentViewModel : CommonViewModel() { avatarEmoji.postValue(address.coreKeyEmojis.extractEmojis().take(1).joinToString("")) checkForDataConsent() + + showRecoverySuccessIfNeeded() } fun processItemClick(item: CommonViewHolderItem) { @@ -127,6 +133,10 @@ class HomeFragmentViewModel : CommonViewModel() { } } + fun handleDeeplink(context: Activity, deepLink: DeepLink) { + deeplinkManager.execute(context, deepLink) + } + private fun updateList() { val list = transactionRepository.list.value ?: return txList.postValue(list.filterIsInstance().sortedByDescending { it.tx.timestamp }.take(TRANSACTION_AMOUNT_HOME_PAGE)) @@ -152,12 +162,38 @@ class HomeFragmentViewModel : CommonViewModel() { } } + private fun showRecoverySuccessIfNeeded() { + if (corePrefRepository.needToShowRecoverySuccessDialog) { + showSimpleDialog( + titleRes = R.string.recovery_success_dialog_title, + descriptionRes = R.string.recovery_success_dialog_description, + closeButtonTextRes = R.string.recovery_success_dialog_close, + onClose = { corePrefRepository.needToShowRecoverySuccessDialog = false }, + ) + } + } + private fun onServiceConnected() { subscribeToEventBus() - viewModelScope.launch(Dispatchers.IO) { - refreshAllData(true) + collectFlow(walletManager.walletEvent) { event -> + when (event) { + is WalletEvent.Tx.TxReceived, + is WalletEvent.Tx.TxReplyReceived, + is WalletEvent.Tx.TxFinalized, + is WalletEvent.Tx.InboundTxBroadcast, + is WalletEvent.Tx.OutboundTxBroadcast, + is WalletEvent.Tx.TxMinedUnconfirmed, + is WalletEvent.Tx.TxMined, + is WalletEvent.Tx.TxFauxMinedUnconfirmed, + is WalletEvent.Tx.TxFauxConfirmed, + is WalletEvent.Tx.TxCancelled -> refreshBalance(false) + + else -> Unit + } } + + refreshAllData(true) } private fun fetchBalanceInfoData() { @@ -178,14 +214,14 @@ class HomeFragmentViewModel : CommonViewModel() { } private fun refreshAllData(isRestarted: Boolean = false) { - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { refreshBalance(isRestarted) transactionRepository.refreshAllData() } } private fun refreshBalance(isRestarted: Boolean = false) { - viewModelScope.launch(Dispatchers.IO) { + launchOnIo { fetchBalanceInfoData() _refreshBalanceInfo.postValue(isRestarted) } @@ -193,16 +229,6 @@ class HomeFragmentViewModel : CommonViewModel() { private fun subscribeToEventBus() { EventBus.subscribe(this) { refreshAllData() } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } - EventBus.subscribe(this) { refreshBalance(false) } EventBus.subscribe(this) { refreshBalance(false) } EventBus.subscribe(this) { onTxSendFailed(it.failureReason) } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt index 057c8f13b..6ddadaee9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R +import com.tari.android.wallet.application.walletManager.WalletManager.WalletEvent import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.collectFlow @@ -80,6 +81,22 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { collectFlow(contactsRepository.contactList) { _listUpdateTrigger.postValue(Unit) } + collectFlow(walletManager.walletEvent) { event -> + when (event) { + is WalletEvent.Tx.TxReceived -> onTxReceived(event.tx) + is WalletEvent.Tx.TxReplyReceived -> onTxReplyReceived(event.tx) + is WalletEvent.Tx.TxFinalized -> onTxFinalized(event.tx) + is WalletEvent.Tx.InboundTxBroadcast -> onInboundTxBroadcast(event.tx) + is WalletEvent.Tx.OutboundTxBroadcast -> onOutboundTxBroadcast(event.tx) + is WalletEvent.Tx.TxMinedUnconfirmed -> onTxMinedUnconfirmed(event.tx) + is WalletEvent.Tx.TxMined -> onTxMined(event.tx) + is WalletEvent.Tx.TxFauxMinedUnconfirmed -> onTxFauxMinedUnconfirmed(event.tx) + is WalletEvent.Tx.TxFauxConfirmed -> onFauxTxMined(event.tx) + is WalletEvent.Tx.TxCancelled -> onTxCancelled(event.tx) + else -> Unit + } + } + viewModelScope.launch(Dispatchers.IO) { updateTxListData() fetchRequiredConfirmationCount() @@ -93,16 +110,6 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { private fun subscribeToEventBus() { EventBus.subscribe(this) { refreshAllData() } - EventBus.subscribe(this) { onTxReceived(it.tx) } - EventBus.subscribe(this) { onTxReplyReceived(it.tx) } - EventBus.subscribe(this) { onTxFinalized(it.tx) } - EventBus.subscribe(this) { onInboundTxBroadcast(it.tx) } - EventBus.subscribe(this) { onOutboundTxBroadcast(it.tx) } - EventBus.subscribe(this) { onTxMinedUnconfirmed(it.tx) } - EventBus.subscribe(this) { onTxMined(it.tx) } - EventBus.subscribe(this) { onTxFauxMinedUnconfirmed(it.tx) } - EventBus.subscribe(this) { onFauxTxMined(it.tx) } - EventBus.subscribe(this) { onTxCancelled(it.tx) } EventBus.subscribe(this) { onTxSendSuccessful(it.txId) } } @@ -153,7 +160,12 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { val nonPendingTxs = (cancelledTxs + nonMinedUnconfirmedCompletedTxs).toMutableList() nonPendingTxs.sortWith(compareByDescending(Tx::timestamp).thenByDescending { it.id }) if (nonPendingTxs.isNotEmpty()) { - items.add(TitleViewHolderItem(title = resourceManager.getString(R.string.home_completed_transactions_title), isFirst = pendingTxs.isEmpty())) + items.add( + TitleViewHolderItem( + title = resourceManager.getString(R.string.home_completed_transactions_title), + isFirst = pendingTxs.isEmpty(), + ) + ) items.addAll(nonPendingTxs.mapIndexed { index, tx -> TransactionItem( tx = tx, diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/CommonTxListViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/CommonTxListViewHolder.kt index 189d80192..4c38f9a49 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/CommonTxListViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/CommonTxListViewHolder.kt @@ -19,7 +19,7 @@ import com.tari.android.wallet.ui.extension.gone import com.tari.android.wallet.ui.extension.string import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.ContactDto -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.util.addressFirstEmojis import com.tari.android.wallet.util.addressLastEmojis import com.tari.android.wallet.util.addressPrefixEmojis @@ -97,7 +97,7 @@ abstract class CommonTxListViewHolder Triple( diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt index 4c1ab3f26..3c815abeb 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt @@ -40,6 +40,7 @@ import android.view.ViewGroup import androidx.fragment.app.viewModels import com.bumptech.glide.Glide import com.tari.android.wallet.R +import com.tari.android.wallet.application.walletManager.WalletFileUtil import com.tari.android.wallet.databinding.FragmentTxDetailsBinding import com.tari.android.wallet.extension.collectNonNullFlow import com.tari.android.wallet.extension.observe @@ -73,7 +74,6 @@ import com.tari.android.wallet.ui.fragment.contactBook.data.contacts.ContactDto import com.tari.android.wallet.ui.fragment.tx.details.gif.GifView import com.tari.android.wallet.ui.fragment.tx.details.gif.GifViewModel import com.tari.android.wallet.ui.fragment.tx.details.gif.TxState -import com.tari.android.wallet.util.WalletUtil import com.tari.android.wallet.util.addressFirstEmojis import com.tari.android.wallet.util.addressLastEmojis import com.tari.android.wallet.util.addressPrefixEmojis @@ -176,7 +176,7 @@ class TxDetailsFragment : CommonFragment string(R.string.tx_detail_payment_cancelled) state.status == MINED_CONFIRMED || state.status == IMPORTED -> @@ -200,7 +200,7 @@ class TxDetailsFragment : CommonFragment(TX_ID_EXTRA_KEY) @@ -89,9 +87,20 @@ class TxDetailsViewModel(savedState: SavedStateHandle) : CommonViewModel() { findTxAndUpdateUI() } - observeTxUpdates() - collectFlow(contactsRepository.contactList) { updateContact() } + + collectFlow(walletManager.walletEvent) { event -> + when (event) { + is WalletEvent.Tx.InboundTxBroadcast -> updateTxData(event.tx) + is WalletEvent.Tx.OutboundTxBroadcast -> updateTxData(event.tx) + is WalletEvent.Tx.TxFinalized -> updateTxData(event.tx) + is WalletEvent.Tx.TxMined -> updateTxData(event.tx) + is WalletEvent.Tx.TxMinedUnconfirmed -> updateTxData(event.tx) + is WalletEvent.Tx.TxReplyReceived -> updateTxData(event.tx) + is WalletEvent.Tx.TxCancelled -> updateTxData(event.tx) + else -> Unit + } + } } fun addOrEditContact() = showEditNameInputs() @@ -165,16 +174,6 @@ class TxDetailsViewModel(savedState: SavedStateHandle) : CommonViewModel() { return reason?.let { resourceManager.getString(it) } ?: "" } - private fun observeTxUpdates() { - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - EventBus.subscribe(this) { updateTxData(it.tx) } - } - private fun updateTxData(tx: Tx) { if (tx.id == this.tx.value?.id) { setTxArg(tx) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/ui/balanceController/BalanceViewController.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/ui/balanceController/BalanceViewController.kt index bf7f381c2..2df036ba8 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/ui/balanceController/BalanceViewController.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/ui/balanceController/BalanceViewController.kt @@ -38,7 +38,7 @@ import android.content.Context import android.view.ViewGroup import com.tari.android.wallet.model.BalanceInfo import com.tari.android.wallet.util.Constants -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import java.lang.ref.WeakReference /** @@ -60,7 +60,7 @@ class BalanceViewController( init { val balance = _balanceInfo.totalBalance - formattedBalance = WalletUtil.balanceFormatter.format(balance.tariValue) + formattedBalance = WalletFileUtil.balanceFormatter.format(balance.tariValue) // decimal tens viewHolders.add(DecimalDigitViewHolder(context, formattedBalance[formattedBalance.length - 1].toString().toInt())) decimalDigitContainerView.addView(viewHolders[0].view, 0) @@ -95,7 +95,7 @@ class BalanceViewController( /* execute setter logic */ _balanceInfo = value val balance = _balanceInfo.totalBalance - formattedBalance = WalletUtil.balanceFormatter.format(balance.tariValue) + formattedBalance = WalletFileUtil.balanceFormatter.format(balance.tariValue) val sizeDiff = formattedBalance.length - viewHolders.size if (sizeDiff <= 0) { // delete items diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/adapters/UtxosTileListViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/adapters/UtxosTileListViewHolder.kt index 0debea7c6..86f8b860b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/adapters/UtxosTileListViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/adapters/UtxosTileListViewHolder.kt @@ -12,7 +12,7 @@ import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolder import com.tari.android.wallet.ui.common.recyclerView.ViewHolderBuilder import com.tari.android.wallet.ui.extension.dpToPx import com.tari.android.wallet.ui.extension.setVisible -import com.tari.android.wallet.util.WalletUtil +import com.tari.android.wallet.application.walletManager.WalletFileUtil import kotlin.random.Random class UtxosTileListViewHolder(view: ItemUtxosTileBinding) : CommonViewHolder(view) { @@ -20,7 +20,7 @@ class UtxosTileListViewHolder(view: ItemUtxosTileBinding) : CommonViewHolder - - - - + app:customFont="heavy" /> + - - - - + android:gravity="center_horizontal" + android:orientation="vertical"> - + - + + + + + tools:visibility="visible"> - - - - - - - - - - - + android:orientation="vertical"> - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_choose_restore_option.xml b/app/src/main/res/layout/fragment_choose_restore_option.xml index afa1d2b10..63455c715 100644 --- a/app/src/main/res/layout/fragment_choose_restore_option.xml +++ b/app/src/main/res/layout/fragment_choose_restore_option.xml @@ -1,7 +1,7 @@ - - @@ -9,6 +9,7 @@ - - - - - + android:layout_height="wrap_content" + android:orientation="vertical" /> - + - + - + - - + + - + - + - + + + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_delete_wallet.xml b/app/src/main/res/layout/fragment_delete_wallet.xml index 278de5827..b40cae880 100644 --- a/app/src/main/res/layout/fragment_delete_wallet.xml +++ b/app/src/main/res/layout/fragment_delete_wallet.xml @@ -1,6 +1,5 @@ - - - - @@ -37,13 +36,14 @@ android:textSize="14sp" app:customFont="medium" /> - - - - - - - - + + + - - + diff --git a/app/src/main/res/layout/fragment_wallet_input_seed_words.xml b/app/src/main/res/layout/fragment_wallet_input_seed_words.xml index 30a783959..bcea3b774 100644 --- a/app/src/main/res/layout/fragment_wallet_input_seed_words.xml +++ b/app/src/main/res/layout/fragment_wallet_input_seed_words.xml @@ -1,6 +1,5 @@ - - @@ -21,7 +20,7 @@ android:layout_height="wrap_content" app:text="@string/restore_from_seed_words_title" /> - @@ -88,8 +87,8 @@ android:layout_width="match_parent" android:layout_height="@dimen/common_action_button_height" android:layout_marginHorizontal="25dp" - android:visibility="gone" android:layout_marginTop="60dp" + android:visibility="gone" app:title="@string/restore_from_seed_words_select_node" /> - - - - - + - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet_restoring_from_seed_words.xml b/app/src/main/res/layout/fragment_wallet_restoring.xml similarity index 93% rename from app/src/main/res/layout/fragment_wallet_restoring_from_seed_words.xml rename to app/src/main/res/layout/fragment_wallet_restoring.xml index e5fc5f4d6..bca109bfd 100644 --- a/app/src/main/res/layout/fragment_wallet_restoring_from_seed_words.xml +++ b/app/src/main/res/layout/fragment_wallet_restoring.xml @@ -1,6 +1,7 @@ @@ -16,7 +17,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.4" /> + app:layout_constraintVertical_bias="0.4" /> + app:layout_constraintTop_toBottomOf="@id/description_view" + tools:text="Waiting for connection" /> + app:layout_constraintTop_toBottomOf="@id/status_label" + tools:text="Attempts 5 out of 10" /> @@ -60,18 +60,34 @@ app:customFont="medium" /> - + app:layout_constraintStart_toStartOf="parent"> + + + + + + android:paddingVertical="8dp" + tools:listitem="@layout/item_phrase_word" /> @@ -109,6 +126,7 @@ android:layout_marginTop="@dimen/write_down_seed_expand_button_top_margin" android:layout_marginEnd="@dimen/write_down_seed_expand_button_end_margin" android:layout_marginBottom="@dimen/write_down_seed_expand_button_bottom_margin" + android:foreground="?attr/selectableItemBackground" android:src="@drawable/vector_recovery_expand_icon" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@+id/list_container" diff --git a/app/src/main/res/layout/view_restore_option.xml b/app/src/main/res/layout/view_restore_option.xml index aed34420d..01d7c3fe7 100644 --- a/app/src/main/res/layout/view_restore_option.xml +++ b/app/src/main/res/layout/view_restore_option.xml @@ -1,6 +1,5 @@ - - - - - + - + - + - - - - + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index efed02fda..c07095c79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -276,6 +276,7 @@ This QR code contains contacts information. \nWould you like to add its to your contact book? This QR code contains contact information. \nWould you like to add it to your contact book? This QR code contains a custom bridge.\nWould you like to save it to the app? + This QR code contains a paper wallet.\nWould you like to save it to the app? @@ -544,6 +545,7 @@ Restore with Google Drive Restore with Local files Restore with Dropbox + Restore from paper wallet Back Up With Seed Phrase @@ -551,6 +553,7 @@ Verify Seed Phrase I understand that if I lose my recovery seed phrase I will not be able to restore my wallet. Error while getting the wallet seed phrase. Please try again later. + Copy seed phrase Verify Seed Phrase @@ -569,6 +572,23 @@ Restore Wallet @string/enter_backup_password_password_match_error You recovered some funds + Invalid QR Code + The QR code you scanned does not contain valid paper wallet seeds + Paper Wallet Detected + You’ve scanned a paper wallet!\nWhat would you like to do? + Restore the wallet + Don\'t restore right now + Sweep the funds + Replace current wallet + Remember to back up first! + Do you want to back up your current wallet before replacing it? If you don\’t back it up first, you will lose all the funds in your current wallet! + Yes, back it up + No, just replace it + Password + Enter the paper wallet password + Password + Incorrect password + The password you entered is incorrect. Please try again. Restore With Seed Phrase @@ -579,24 +599,24 @@ Select Custom Base Node Edit Base Node Oops, looks like the address you entered isn\'t good. We had to revert your change. - At least one of the entered seed words is invalid. Please review them and try again You entered not enough seed words. Please add some more and try again Unable to generate key from seed words. Please check your seed words and try again - This could take between 1 to 3 minutes.\nPlease keep the app open - Waiting for connection Connection established. The process will start soon Restoring in progress: Attempt %s out of %s - Recovery Failed Unable to connect with base node. Please try again later Unable to recover your wallet due to the error - Start typing to see suggestions here are no suggestions for the phrase + Are you sure you want to cancel? + You will lose your progress, but don\'t worry, you can always start over. + You’re all set! + Your funds have been imported and are being processed 😊 + Awesome, thanks! Enter your backup password diff --git a/build.gradle b/build.gradle index 76b914395..64f797132 100644 --- a/build.gradle +++ b/build.gradle @@ -6,12 +6,12 @@ buildscript { ext.coroutines_version = '1.8.0' // build & version - ext.buildNumber = 306 - ext.versionNumber = "0.27.1" + ext.buildNumber = 311 + ext.versionNumber = "0.28.0" // JNI libs ext.libwalletHostURL = "https://github.com/tari-project/tari/releases/download/" - ext.libwalletVersion = "v1.4.1-rc.0" + ext.libwalletVersion = "v1.7.0-rc.1" ext.libwalletMinValidVersion = "v1.4.1-rc.0" ext.libwalletx64A = "libminotari_wallet_ffi.android_x86_64.a" ext.libwalletArmA = "libminotari_wallet_ffi.android_aarch64.a"