Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/src/main/java/com/electricdreams/numo/ModernPOSActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.electricdreams.numo
import com.electricdreams.numo.R
import android.widget.Toast

import android.app.Activity
import android.app.PendingIntent
Expand Down Expand Up @@ -166,6 +166,13 @@ class ModernPOSActivity : AppCompatActivity(), SatocashWallet.OperationFeedback,
// Lifecycle methods
override fun onResume() {
super.onResume()
CashuWalletManager.setErrorListener(object : CashuWalletManager.WalletErrorListener {
override fun onWalletError(message: String) {
runOnUiThread {
Toast.makeText(this@ModernPOSActivity, message, Toast.LENGTH_LONG).show()
}
}
})

// Reapply theme when returning from settings
uiCoordinator.applyTheme()
Expand All @@ -184,10 +191,12 @@ class ModernPOSActivity : AppCompatActivity(), SatocashWallet.OperationFeedback,

override fun onPause() {
super.onPause()
CashuWalletManager.setErrorListener(null)
nfcAdapter?.disableForegroundDispatch(this)
}

override fun onDestroy() {
CashuWalletManager.setErrorListener(null)
uiCoordinator.stopServices()
bitcoinPriceWorker?.stop()
super.onDestroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ object CashuWalletManager : MintManager.MintChangeListener {
// Build initial wallet
val initialMints = mintManager.getAllowedMints()
scope.launch {
rebuildWallet(initialMints)
try {
rebuildWallet(initialMints)
} catch (t: Throwable) {
Log.e(TAG, "Failed to rebuild wallet during init", t)
notifyWalletError("Wallet initialization failed: ${t.localizedMessage ?: "Unknown error"}")
}
}
}

Expand Down Expand Up @@ -174,13 +179,23 @@ object CashuWalletManager : MintManager.MintChangeListener {
override fun onMintsChanged(newMints: List<String>) {
Log.d(TAG, "Mint list changed, rebuilding wallet with ${'$'}{newMints.size} mints")
scope.launch {
rebuildWallet(newMints)
try {
rebuildWallet(newMints)
} catch (t: Throwable) {
Log.e(TAG, "Failed to rebuild wallet after mint change", t)
notifyWalletError("Unable to update wallet: ${t.localizedMessage ?: "Unknown error"}")
}
}
}

/** Current MultiMintWallet instance, or null if initialization failed or not complete. */
fun getWallet(): MultiMintWallet? = wallet

/** Set an optional callback that surfaces wallet errors to the UI layer. */
fun setErrorListener(listener: WalletErrorListener?) {
walletErrorListener = listener
}

/** Current database instance, mostly for debugging or future use. */
fun getDatabase(): WalletSqliteDatabase? = database

Expand Down Expand Up @@ -416,6 +431,17 @@ object CashuWalletManager : MintManager.MintChangeListener {
Log.d(TAG, "Initialized MultiMintWallet with ${'$'}{mints.size} mints; DB=${'$'}{dbFile.absolutePath}")
} catch (t: Throwable) {
Log.e(TAG, "Failed to initialize MultiMintWallet", t)
notifyWalletError("Wallet initialization failed: ${t.localizedMessage ?: "Unknown error"}")
}
}

private fun notifyWalletError(message: String) {
walletErrorListener?.onWalletError(message)
}

interface WalletErrorListener {
fun onWalletError(message: String)
}

private var walletErrorListener: WalletErrorListener? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.lifecycle.lifecycleScope
import android.util.Log
import com.electricdreams.numo.R
import com.electricdreams.numo.core.cashu.CashuWalletManager
import com.electricdreams.numo.core.model.Amount
Expand Down Expand Up @@ -208,42 +209,51 @@ class MintsSettingsActivity : AppCompatActivity() {
}

lifecycleScope.launch {
// Load balances
val balances = withContext(Dispatchers.IO) {
CashuWalletManager.getAllMintBalances()
}
mintBalances.clear()
mintBalances.putAll(balances)

// Auto-select lightning mint if none selected
if (selectedLightningMint == null || !mints.contains(selectedLightningMint)) {
val highestBalanceMint = mints.maxByOrNull { mintBalances[it] ?: 0L }
highestBalanceMint?.let { setLightningMint(it, animate = false) }
try {
// Load balances
val balances = withContext(Dispatchers.IO) {
CashuWalletManager.getAllMintBalances()
}
mintBalances.clear()
mintBalances.putAll(balances)

// Auto-select lightning mint if none selected
if (selectedLightningMint == null || !mints.contains(selectedLightningMint)) {
val highestBalanceMint = mints.maxByOrNull { mintBalances[it] ?: 0L }
highestBalanceMint?.let { setLightningMint(it, animate = false) }
}

// Build UI
buildMintList(mints)
updateLightningMintCard()
updateTotalBalance()

// Refresh stale mint info
refreshStaleMintInfo()
} catch (t: Throwable) {
Log.e(TAG, "Failed to load mint balances", t)
Toast.makeText(this@MintsSettingsActivity, getString(R.string.error_loading_mints), Toast.LENGTH_LONG).show()
}

// Build UI
buildMintList(mints)
updateLightningMintCard()
updateTotalBalance()

// Refresh stale mint info
refreshStaleMintInfo()
}
}

private fun refreshBalances() {
lifecycleScope.launch {
val balances = withContext(Dispatchers.IO) {
CashuWalletManager.getAllMintBalances()
try {
val balances = withContext(Dispatchers.IO) {
CashuWalletManager.getAllMintBalances()
}
mintBalances.clear()
mintBalances.putAll(balances)

// Update UI
val mints = mintManager.getAllowedMints()
buildMintList(mints)
updateLightningMintCard()
updateTotalBalance()
} catch (t: Throwable) {
Log.e(TAG, "Failed to refresh mint balances", t)
}
mintBalances.clear()
mintBalances.putAll(balances)

// Update UI
val mints = mintManager.getAllowedMints()
buildMintList(mints)
updateLightningMintCard()
updateTotalBalance()
}
}

Expand Down Expand Up @@ -409,36 +419,41 @@ class MintsSettingsActivity : AppCompatActivity() {
addMintCard.setLoading(true)

lifecycleScope.launch {
val isValid = validateMintUrl(mintUrl)

if (!isValid) {
addMintCard.setLoading(false)
Toast.makeText(
this@MintsSettingsActivity,
getString(R.string.mints_invalid_url),
Toast.LENGTH_LONG
).show()
return@launch
}

val added = mintManager.addMint(mintUrl)
if (added) {
fetchAndStoreMintInfo(mintUrl)
loadMintsAndBalances()
addMintCard.clearInput()
addMintCard.collapseIfExpanded()

// Broadcast that mints changed so other activities can refresh
BalanceRefreshBroadcast.send(this@MintsSettingsActivity, BalanceRefreshBroadcast.REASON_MINT_ADDED)
try {
val isValid = validateMintUrl(mintUrl)

Toast.makeText(
this@MintsSettingsActivity,
getString(R.string.mints_added_toast),
Toast.LENGTH_SHORT
).show()
if (!isValid) {
addMintCard.setLoading(false)
Toast.makeText(
this@MintsSettingsActivity,
getString(R.string.mints_invalid_url),
Toast.LENGTH_LONG
).show()
return@launch
}

val added = mintManager.addMint(mintUrl)
if (added) {
fetchAndStoreMintInfo(mintUrl)
loadMintsAndBalances()
addMintCard.clearInput()
addMintCard.collapseIfExpanded()

// Broadcast that mints changed so other activities can refresh
BalanceRefreshBroadcast.send(this@MintsSettingsActivity, BalanceRefreshBroadcast.REASON_MINT_ADDED)

Toast.makeText(
this@MintsSettingsActivity,
getString(R.string.mints_added_toast),
Toast.LENGTH_SHORT
).show()
}
} catch (t: Throwable) {
Log.e(TAG, "Failed to add mint", t)
Toast.makeText(this@MintsSettingsActivity, R.string.mints_add_failed, Toast.LENGTH_LONG).show()
} finally {
addMintCard.setLoading(false)
}

addMintCard.setLoading(false)
}
}

Expand Down Expand Up @@ -478,29 +493,37 @@ class MintsSettingsActivity : AppCompatActivity() {

private suspend fun fetchAndStoreMintInfo(mintUrl: String) {
withContext(Dispatchers.IO) {
val info = CashuWalletManager.fetchMintInfo(mintUrl)
if (info != null) {
val json = CashuWalletManager.mintInfoToJson(info)
mintManager.setMintInfo(mintUrl, json)
mintManager.setMintRefreshTimestamp(mintUrl)

info.iconUrl?.let { iconUrl ->
if (iconUrl.isNotEmpty()) {
MintIconCache.downloadAndCacheIcon(mintUrl, iconUrl)
try {
val info = CashuWalletManager.fetchMintInfo(mintUrl)
if (info != null) {
val json = CashuWalletManager.mintInfoToJson(info)
mintManager.setMintInfo(mintUrl, json)
mintManager.setMintRefreshTimestamp(mintUrl)

info.iconUrl?.let { iconUrl ->
if (iconUrl.isNotEmpty()) {
MintIconCache.downloadAndCacheIcon(mintUrl, iconUrl)
}
}
}
} catch (t: Throwable) {
Log.w(TAG, "Failed to fetch mint info for ${mintUrl}", t)
}
}
}

private fun refreshStaleMintInfo() {
lifecycleScope.launch {
val mintsToRefresh = mintManager.getMintsNeedingRefresh()
for (mintUrl in mintsToRefresh) {
fetchAndStoreMintInfo(mintUrl)
}
if (mintsToRefresh.isNotEmpty()) {
updateLightningMintCard()
try {
val mintsToRefresh = mintManager.getMintsNeedingRefresh()
for (mintUrl in mintsToRefresh) {
fetchAndStoreMintInfo(mintUrl)
}
if (mintsToRefresh.isNotEmpty()) {
updateLightningMintCard()
}
} catch (t: Throwable) {
Log.w(TAG, "Failed to refresh mint info", t)
}
}
}
Expand Down
Loading