loading state in ui store

This commit is contained in:
callebtc
2025-07-15 18:14:51 +02:00
parent c4f39f7f82
commit 9b7093b8f0
6 changed files with 98 additions and 55 deletions
@@ -119,16 +119,30 @@ fun ReceiveEcashDialog(
.fillMaxWidth() .fillMaxWidth()
.height(56.dp), .height(56.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851), containerColor = if (isLoading) Color(0xFF2A2A2A) else Color(0xFF00C851),
disabledContainerColor = Color(0xFF2A2A2A) disabledContainerColor = Color(0xFF2A2A2A)
), ),
shape = RoundedCornerShape(16.dp) shape = RoundedCornerShape(16.dp)
) { ) {
if (isLoading) { if (isLoading) {
CircularProgressIndicator( Row(
color = Color.Black, verticalAlignment = Alignment.CenterVertically
modifier = Modifier.size(24.dp) ) {
) CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "RECEIVING TOKEN...",
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
} else { } else {
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -161,8 +175,11 @@ fun ReceiveEcashDialog(
OutlinedTextField( OutlinedTextField(
value = token, value = token,
onValueChange = { onValueChange = {
viewModel.setTokenInput(it) if (!isLoading) {
viewModel.setTokenInput(it)
}
}, },
enabled = !isLoading,
label = { label = {
Text( Text(
text = "Cashu Token", text = "Cashu Token",
@@ -182,7 +199,10 @@ fun ReceiveEcashDialog(
unfocusedBorderColor = Color(0xFF2A2A2A), unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray, unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White, unfocusedTextColor = Color.White,
focusedTextColor = Color.White focusedTextColor = Color.White,
disabledBorderColor = Color.Gray,
disabledLabelColor = Color.Gray,
disabledTextColor = Color.Gray
), ),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -193,13 +213,16 @@ fun ReceiveEcashDialog(
trailingIcon = { trailingIcon = {
IconButton( IconButton(
onClick = { onClick = {
// TODO: Implement QR code scanning if (!isLoading) {
} // TODO: Implement QR code scanning
}
},
enabled = !isLoading
) { ) {
Icon( Icon(
imageVector = Icons.Filled.QrCode, imageVector = Icons.Filled.QrCode,
contentDescription = "Scan QR", contentDescription = "Scan QR",
tint = Color(0xFF00C851) tint = if (isLoading) Color.Gray else Color(0xFF00C851)
) )
} }
} }
@@ -214,6 +237,7 @@ fun ReceiveEcashDialog(
} }
} }
}, },
enabled = !isLoading,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(56.dp), .height(56.dp),
@@ -222,7 +246,7 @@ fun ReceiveEcashDialog(
), ),
border = BorderStroke( border = BorderStroke(
2.dp, 2.dp,
Color(0xFF00C851) if (isLoading) Color.Gray else Color(0xFF00C851)
), ),
shape = RoundedCornerShape(16.dp) shape = RoundedCornerShape(16.dp)
) { ) {
@@ -232,13 +256,13 @@ fun ReceiveEcashDialog(
Icon( Icon(
imageVector = Icons.Filled.ContentPaste, imageVector = Icons.Filled.ContentPaste,
contentDescription = "Paste", contentDescription = "Paste",
tint = Color(0xFF00C851), tint = if (isLoading) Color.Gray else Color(0xFF00C851),
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text( Text(
text = "PASTE FROM CLIPBOARD", text = "PASTE FROM CLIPBOARD",
color = Color(0xFF00C851), color = if (isLoading) Color.Gray else Color(0xFF00C851),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
@@ -137,7 +137,8 @@ fun SendEcashDialog(
// Amount input // Amount input
OutlinedTextField( OutlinedTextField(
value = amount, value = amount,
onValueChange = { amount = it }, onValueChange = { if (!isLoading) amount = it },
enabled = !isLoading,
label = { label = {
Text( Text(
text = "Amount (sats)", text = "Amount (sats)",
@@ -151,7 +152,10 @@ fun SendEcashDialog(
unfocusedBorderColor = Color(0xFF2A2A2A), unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray, unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White, unfocusedTextColor = Color.White,
focusedTextColor = Color.White focusedTextColor = Color.White,
disabledBorderColor = Color.Gray,
disabledLabelColor = Color.Gray,
disabledTextColor = Color.Gray
), ),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -162,7 +166,8 @@ fun SendEcashDialog(
// Memo input // Memo input
OutlinedTextField( OutlinedTextField(
value = memo, value = memo,
onValueChange = { memo = it }, onValueChange = { if (!isLoading) memo = it },
enabled = !isLoading,
label = { label = {
Text( Text(
text = "Memo (optional)", text = "Memo (optional)",
@@ -175,7 +180,10 @@ fun SendEcashDialog(
unfocusedBorderColor = Color(0xFF2A2A2A), unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray, unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White, unfocusedTextColor = Color.White,
focusedTextColor = Color.White focusedTextColor = Color.White,
disabledBorderColor = Color.Gray,
disabledLabelColor = Color.Gray,
disabledTextColor = Color.Gray
), ),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -195,16 +203,30 @@ fun SendEcashDialog(
.fillMaxWidth() .fillMaxWidth()
.height(56.dp), .height(56.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851), containerColor = if (isLoading) Color(0xFF2A2A2A) else Color(0xFF00C851),
disabledContainerColor = Color(0xFF2A2A2A) disabledContainerColor = Color(0xFF2A2A2A)
), ),
shape = RoundedCornerShape(16.dp) shape = RoundedCornerShape(16.dp)
) { ) {
if (isLoading) { if (isLoading) {
CircularProgressIndicator( Row(
color = Color.Black, verticalAlignment = Alignment.CenterVertically
modifier = Modifier.size(24.dp) ) {
) CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "CREATING TOKEN...",
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
} else { } else {
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -15,7 +15,8 @@ import java.util.*
class LightningManager( class LightningManager(
private val repository: WalletRepository, private val repository: WalletRepository,
private val cashuService: CashuService, private val cashuService: CashuService,
private val coroutineScope: CoroutineScope private val coroutineScope: CoroutineScope,
private val uiStateManager: UIStateManager
) { ) {
companion object { companion object {
private const val TAG = "LightningManager" private const val TAG = "LightningManager"
@@ -34,9 +35,6 @@ class LightningManager(
private val _pendingMeltQuotes = MutableLiveData<List<MeltQuote>>(emptyList()) private val _pendingMeltQuotes = MutableLiveData<List<MeltQuote>>(emptyList())
val pendingMeltQuotes: androidx.lifecycle.LiveData<List<MeltQuote>> = _pendingMeltQuotes val pendingMeltQuotes: androidx.lifecycle.LiveData<List<MeltQuote>> = _pendingMeltQuotes
private val _isLoading = MutableLiveData<Boolean>(false)
val isLoading: androidx.lifecycle.LiveData<Boolean> = _isLoading
private val _errorMessage = MutableLiveData<String?>(null) private val _errorMessage = MutableLiveData<String?>(null)
val errorMessage: androidx.lifecycle.LiveData<String?> = _errorMessage val errorMessage: androidx.lifecycle.LiveData<String?> = _errorMessage
@@ -46,7 +44,7 @@ class LightningManager(
fun createMintQuote(amount: Long, description: String? = null) { fun createMintQuote(amount: Long, description: String? = null) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.createMintQuote(amount, description).onSuccess { quote -> cashuService.createMintQuote(amount, description).onSuccess { quote ->
_currentMintQuote.value = quote _currentMintQuote.value = quote
@@ -61,7 +59,7 @@ class LightningManager(
_errorMessage.value = "Failed to create invoice: ${error.message}" _errorMessage.value = "Failed to create invoice: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -72,7 +70,7 @@ class LightningManager(
fun createMeltQuote(invoice: String, onQuoteCreated: () -> Unit = {}) { fun createMeltQuote(invoice: String, onQuoteCreated: () -> Unit = {}) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.createMeltQuote(invoice).onSuccess { quote -> cashuService.createMeltQuote(invoice).onSuccess { quote ->
_currentMeltQuote.value = quote _currentMeltQuote.value = quote
@@ -88,7 +86,7 @@ class LightningManager(
_errorMessage.value = "Failed to process invoice: ${error.message}" _errorMessage.value = "Failed to process invoice: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -104,7 +102,7 @@ class LightningManager(
) { ) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.payInvoice(quoteId).onSuccess { success -> cashuService.payInvoice(quoteId).onSuccess { success ->
if (success) { if (success) {
// Update quote status and add transaction // Update quote status and add transaction
@@ -143,7 +141,7 @@ class LightningManager(
_errorMessage.value = "Payment failed: ${error.message}" _errorMessage.value = "Payment failed: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -16,7 +16,8 @@ import java.util.*
class MintManager( class MintManager(
private val repository: WalletRepository, private val repository: WalletRepository,
private val cashuService: CashuService, private val cashuService: CashuService,
private val coroutineScope: CoroutineScope private val coroutineScope: CoroutineScope,
private val uiStateManager: UIStateManager
) { ) {
companion object { companion object {
private const val TAG = "MintManager" private const val TAG = "MintManager"
@@ -120,7 +121,7 @@ class MintManager(
*/ */
private suspend fun initializeWalletWithMint(mintUrl: String, onWalletInitialized: () -> Unit) { private suspend fun initializeWalletWithMint(mintUrl: String, onWalletInitialized: () -> Unit) {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.initializeWallet(mintUrl).onSuccess { cashuService.initializeWallet(mintUrl).onSuccess {
onWalletInitialized() onWalletInitialized()
Log.d(TAG, "Wallet initialized with mint: $mintUrl") Log.d(TAG, "Wallet initialized with mint: $mintUrl")
@@ -129,7 +130,7 @@ class MintManager(
_errorMessage.value = "Failed to connect to mint: ${error.message}" _errorMessage.value = "Failed to connect to mint: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
@@ -154,7 +155,7 @@ class MintManager(
fun addMint(mintUrl: String, nickname: String, onSuccess: () -> Unit = {}) { fun addMint(mintUrl: String, nickname: String, onSuccess: () -> Unit = {}) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.getMintInfo(mintUrl).onSuccess { mintInfo -> cashuService.getMintInfo(mintUrl).onSuccess { mintInfo ->
val mint = Mint( val mint = Mint(
url = mintUrl, url = mintUrl,
@@ -184,7 +185,7 @@ class MintManager(
_errorMessage.value = "Failed to connect to mint: ${error.message}" _errorMessage.value = "Failed to connect to mint: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -195,7 +196,7 @@ class MintManager(
fun setActiveMint(mintUrl: String, onWalletInitialized: () -> Unit = {}) { fun setActiveMint(mintUrl: String, onWalletInitialized: () -> Unit = {}) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
repository.setActiveMint(mintUrl).onSuccess { repository.setActiveMint(mintUrl).onSuccess {
_activeMint.value = mintUrl _activeMint.value = mintUrl
initializeWalletWithMint(mintUrl, onWalletInitialized) initializeWalletWithMint(mintUrl, onWalletInitialized)
@@ -203,7 +204,7 @@ class MintManager(
_errorMessage.value = "Failed to set active mint: ${error.message}" _errorMessage.value = "Failed to set active mint: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -235,7 +236,7 @@ class MintManager(
fun syncAllMints() { fun syncAllMints() {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
val currentMints = _mints.value ?: emptyList() val currentMints = _mints.value ?: emptyList()
for (mint in currentMints) { for (mint in currentMints) {
@@ -265,7 +266,7 @@ class MintManager(
Log.e(TAG, "Error syncing mints", e) Log.e(TAG, "Error syncing mints", e)
_errorMessage.value = "Failed to sync mints: ${e.message}" _errorMessage.value = "Failed to sync mints: ${e.message}"
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -21,7 +21,8 @@ import java.util.*
class TokenManager( class TokenManager(
private val repository: WalletRepository, private val repository: WalletRepository,
private val cashuService: CashuService, private val cashuService: CashuService,
private val coroutineScope: CoroutineScope private val coroutineScope: CoroutineScope,
private val uiStateManager: UIStateManager
) { ) {
companion object { companion object {
private const val TAG = "TokenManager" private const val TAG = "TokenManager"
@@ -37,9 +38,6 @@ class TokenManager(
private val _tokenInput = MutableLiveData<String>("") private val _tokenInput = MutableLiveData<String>("")
val tokenInput: androidx.lifecycle.LiveData<String> = _tokenInput val tokenInput: androidx.lifecycle.LiveData<String> = _tokenInput
private val _isLoading = MutableLiveData<Boolean>(false)
val isLoading: androidx.lifecycle.LiveData<Boolean> = _isLoading
private val _errorMessage = MutableLiveData<String?>(null) private val _errorMessage = MutableLiveData<String?>(null)
val errorMessage: androidx.lifecycle.LiveData<String?> = _errorMessage val errorMessage: androidx.lifecycle.LiveData<String?> = _errorMessage
@@ -54,7 +52,7 @@ class TokenManager(
) { ) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.createToken(amount, memo).onSuccess { token -> cashuService.createToken(amount, memo).onSuccess { token ->
_generatedToken.value = token _generatedToken.value = token
@@ -80,7 +78,7 @@ class TokenManager(
_errorMessage.value = "Failed to create token: ${error.message}" _errorMessage.value = "Failed to create token: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -141,14 +139,14 @@ class TokenManager(
fun decodeCashuToken(token: String) { fun decodeCashuToken(token: String) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
cashuService.decodeToken(token).onSuccess { decodedToken -> cashuService.decodeToken(token).onSuccess { decodedToken ->
_decodedToken.value = decodedToken _decodedToken.value = decodedToken
}.onFailure { error -> }.onFailure { error ->
_errorMessage.value = "Invalid token: ${error.message}" _errorMessage.value = "Invalid token: ${error.message}"
} }
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -167,7 +165,7 @@ class TokenManager(
) { ) {
coroutineScope.launch { coroutineScope.launch {
try { try {
_isLoading.value = true uiStateManager.setLoading(true)
// Get decoded token // Get decoded token
val decodedToken = _decodedToken.value val decodedToken = _decodedToken.value
@@ -257,7 +255,7 @@ class TokenManager(
) )
onFailure(failureData) onFailure(failureData)
} finally { } finally {
_isLoading.value = false uiStateManager.setLoading(false)
} }
} }
} }
@@ -24,11 +24,11 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
private val cashuService = CashuService.getInstance() private val cashuService = CashuService.getInstance()
// Manager instances // Manager instances
private val mintManager = MintManager(repository, cashuService, viewModelScope)
private val tokenManager = TokenManager(repository, cashuService, viewModelScope)
private val lightningManager = LightningManager(repository, cashuService, viewModelScope)
private val transactionManager = TransactionManager(repository, viewModelScope)
private val uiStateManager = UIStateManager() private val uiStateManager = UIStateManager()
private val mintManager = MintManager(repository, cashuService, viewModelScope, uiStateManager)
private val tokenManager = TokenManager(repository, cashuService, viewModelScope, uiStateManager)
private val lightningManager = LightningManager(repository, cashuService, viewModelScope, uiStateManager)
private val transactionManager = TransactionManager(repository, viewModelScope)
companion object { companion object {
private const val TAG = "WalletViewModel" private const val TAG = "WalletViewModel"