Browse Source

Implement base URL management in settings, allowing users to save and retrieve the base URL. Update UI components in SettingsContent for base URL input and feedback. Refactor dependency injection to provide base URL dynamically in KtorApiService. Remove WalletScreen and WorkersScreen implementations as part of the refactor.

Carles Sentis 12 hours ago
parent
commit
a5621049cd
20 changed files with 152 additions and 382 deletions
  1. 1 0
      app/src/main/java/com/codeskraps/publicpool/data/local/PreferencesKeys.kt
  2. 10 10
      app/src/main/java/com/codeskraps/publicpool/data/remote/KtorApiService.kt
  3. 28 2
      app/src/main/java/com/codeskraps/publicpool/data/repository/PublicPoolRepositoryImpl.kt
  4. 10 1
      app/src/main/java/com/codeskraps/publicpool/di/DataModule.kt
  5. 4 0
      app/src/main/java/com/codeskraps/publicpool/di/DomainModule.kt
  6. 4 0
      app/src/main/java/com/codeskraps/publicpool/domain/repository/PublicPoolRepository.kt
  7. 10 0
      app/src/main/java/com/codeskraps/publicpool/domain/usecase/GetBaseUrlUseCase.kt
  8. 9 0
      app/src/main/java/com/codeskraps/publicpool/domain/usecase/SaveBaseUrlUseCase.kt
  9. 2 2
      app/src/main/java/com/codeskraps/publicpool/presentation/navigation/BottomTabs.kt
  10. 19 9
      app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsContent.kt
  11. 6 2
      app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsMvi.kt
  12. 34 14
      app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsScreenModel.kt
  13. 0 339
      app/src/main/java/com/codeskraps/publicpool/presentation/wallet/WalletScreen.kt
  14. 2 2
      app/src/main/java/com/codeskraps/publicpool/presentation/workers/WorkersContent.kt
  15. 2 0
      app/src/main/res/values-de/strings.xml
  16. 2 0
      app/src/main/res/values-es/strings.xml
  17. 2 0
      app/src/main/res/values-fr/strings.xml
  18. 2 0
      app/src/main/res/values-hi/strings.xml
  19. 2 0
      app/src/main/res/values-zh-rCN/strings.xml
  20. 3 1
      app/src/main/res/values/strings.xml

+ 1 - 0
app/src/main/java/com/codeskraps/publicpool/data/local/PreferencesKeys.kt

@@ -4,4 +4,5 @@ import androidx.datastore.preferences.core.stringPreferencesKey
 
 object PreferencesKeys {
     val WALLET_ADDRESS = stringPreferencesKey("wallet_address")
+    val BASE_URL = stringPreferencesKey("base_url")
 } 

+ 10 - 10
app/src/main/java/com/codeskraps/publicpool/data/remote/KtorApiService.kt

@@ -25,28 +25,28 @@ interface KtorApiService {
 
     // We can add implementations here or in a separate class
     companion object {
-        const val BASE_URL = "https://public-pool.io:40557/api" // Define base URL
-        const val BLOCKCHAIN_INFO_BASE_URL = "https://blockchain.info" // New base URL
-        const val BINANCE_BASE_URL = "https://api.binance.com" // Binance base URL
+        const val DEFAULT_BASE_URL = "https://public-pool.io:40557/api"
+        const val BLOCKCHAIN_INFO_BASE_URL = "https://blockchain.info"
+        const val BINANCE_BASE_URL = "https://api.binance.com"
     }
 }
 
 // Example Implementation (can be provided via Koin later)
-class KtorApiServiceImpl(private val client: HttpClient) : KtorApiService {
+class KtorApiServiceImpl(
+    private val client: HttpClient,
+    private val baseUrlProvider: () -> String = { KtorApiService.DEFAULT_BASE_URL }
+) : KtorApiService {
 
     override suspend fun getClientInfo(walletAddress: String): ClientInfoDto {
-        return client.get("${KtorApiService.BASE_URL}/client/$walletAddress").body()
+        return client.get("${baseUrlProvider()}/client/$walletAddress").body()
     }
 
     override suspend fun getNetworkInfo(): NetworkInfoDto {
-        return client.get("${KtorApiService.BASE_URL}/network").body()
+        return client.get("${baseUrlProvider()}/network").body()
     }
 
     override suspend fun getChartData(walletAddress: String): List<ChartDataPointDto> {
-        // Use the correct URL with /chart and expect a List directly
-        return client.get("${KtorApiService.BASE_URL}/client/$walletAddress/chart").body<List<ChartDataPointDto>>()
-        // Or let type inference work:
-        // return client.get("${KtorApiService.BASE_URL}/client/$walletAddress/chart").body()
+        return client.get("${baseUrlProvider()}/client/$walletAddress/chart").body()
     }
 
     // Implementation for new function

+ 28 - 2
app/src/main/java/com/codeskraps/publicpool/data/repository/PublicPoolRepositoryImpl.kt

@@ -86,11 +86,37 @@ class PublicPoolRepositoryImpl(
             }
        } catch(e: Exception) {
             Log.e(TAG, "Failed to save wallet address to DataStore", e)
-            // Optionally rethrow or handle the error based on requirements (e.g., return Result<Unit>)
-            // For now, just log the error.
+            throw e
        }
     }
 
+    // --- Base URL Management ---
+    override fun getBaseUrl(): Flow<String> {
+        return dataStore.data
+            .catch { exception ->
+                if (exception is IOException) {
+                    Log.e(TAG, "Error reading base URL from DataStore", exception)
+                    emit(emptyPreferences())
+                } else {
+                    throw exception
+                }
+            }
+            .map { preferences ->
+                preferences[PreferencesKeys.BASE_URL] ?: KtorApiService.DEFAULT_BASE_URL
+            }
+    }
+
+    override suspend fun saveBaseUrl(url: String) {
+        try {
+            dataStore.edit { preferences ->
+                preferences[PreferencesKeys.BASE_URL] = url
+            }
+        } catch(e: Exception) {
+            Log.e(TAG, "Failed to save base URL to DataStore", e)
+            throw e
+        }
+    }
+
     // --- Blockchain.info Data ---
     override suspend fun getBlockchainWalletInfo(walletAddress: String): Result<WalletInfo> {
         return try {

+ 10 - 1
app/src/main/java/com/codeskraps/publicpool/di/DataModule.kt

@@ -20,6 +20,8 @@ import kotlinx.serialization.json.Json
 import org.koin.core.module.dsl.singleOf
 import org.koin.dsl.bind
 import org.koin.dsl.module
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 
 val dataModule = module {
 
@@ -46,7 +48,14 @@ val dataModule = module {
     }
 
     // API Service Implementation
-    singleOf(::KtorApiServiceImpl) bind KtorApiService::class
+    single<KtorApiService> {
+        KtorApiServiceImpl(
+            client = get(),
+            baseUrlProvider = {
+                runBlocking { get<PublicPoolRepository>().getBaseUrl().first() }
+            }
+        )
+    }
 
     // Repository Implementation
     singleOf(::PublicPoolRepositoryImpl) bind PublicPoolRepository::class

+ 4 - 0
app/src/main/java/com/codeskraps/publicpool/di/DomainModule.kt

@@ -1,6 +1,7 @@
 package com.codeskraps.publicpool.di
 
 import com.codeskraps.publicpool.domain.usecase.CalculateTwoHourAverageUseCase
+import com.codeskraps.publicpool.domain.usecase.GetBaseUrlUseCase
 import com.codeskraps.publicpool.domain.usecase.GetBlockchainWalletInfoUseCase
 import com.codeskraps.publicpool.domain.usecase.GetBtcPriceUseCase
 import com.codeskraps.publicpool.domain.usecase.GetChartDataUseCase
@@ -9,6 +10,7 @@ import com.codeskraps.publicpool.domain.usecase.GetNetworkInfoUseCase
 import com.codeskraps.publicpool.domain.usecase.GetWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.IdentifyUserUseCase
 import com.codeskraps.publicpool.domain.usecase.InitializeAnalyticsUseCase
+import com.codeskraps.publicpool.domain.usecase.SaveBaseUrlUseCase
 import com.codeskraps.publicpool.domain.usecase.SaveWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackEventUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackPageViewUseCase
@@ -19,6 +21,8 @@ val domainModule = module {
     // Use Case providers
     factoryOf(::GetWalletAddressUseCase)
     factoryOf(::SaveWalletAddressUseCase)
+    factoryOf(::GetBaseUrlUseCase)
+    factoryOf(::SaveBaseUrlUseCase)
     factoryOf(::GetNetworkInfoUseCase)
     factoryOf(::GetClientInfoUseCase)
     factoryOf(::GetChartDataUseCase)

+ 4 - 0
app/src/main/java/com/codeskraps/publicpool/domain/repository/PublicPoolRepository.kt

@@ -20,6 +20,10 @@ interface PublicPoolRepository {
     fun getWalletAddress(): Flow<String?> // Flow to observe changes
     suspend fun saveWalletAddress(address: String)
 
+    // --- Base URL Management ---
+    fun getBaseUrl(): Flow<String>
+    suspend fun saveBaseUrl(url: String)
+
     // --- Blockchain.info Data ---
     suspend fun getBlockchainWalletInfo(walletAddress: String): Result<WalletInfo>
 

+ 10 - 0
app/src/main/java/com/codeskraps/publicpool/domain/usecase/GetBaseUrlUseCase.kt

@@ -0,0 +1,10 @@
+package com.codeskraps.publicpool.domain.usecase
+
+import com.codeskraps.publicpool.domain.repository.PublicPoolRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetBaseUrlUseCase(
+    private val repository: PublicPoolRepository
+) {
+    operator fun invoke(): Flow<String> = repository.getBaseUrl()
+} 

+ 9 - 0
app/src/main/java/com/codeskraps/publicpool/domain/usecase/SaveBaseUrlUseCase.kt

@@ -0,0 +1,9 @@
+package com.codeskraps.publicpool.domain.usecase
+
+import com.codeskraps.publicpool.domain.repository.PublicPoolRepository
+
+class SaveBaseUrlUseCase(
+    private val repository: PublicPoolRepository
+) {
+    suspend operator fun invoke(url: String) = repository.saveBaseUrl(url)
+} 

+ 2 - 2
app/src/main/java/com/codeskraps/publicpool/presentation/navigation/BottomTabs.kt

@@ -11,7 +11,7 @@ import cafe.adriel.voyager.navigator.tab.Tab
 import cafe.adriel.voyager.navigator.tab.TabOptions
 import com.codeskraps.publicpool.R
 import com.codeskraps.publicpool.presentation.wallet.WalletContent
-import com.codeskraps.publicpool.presentation.workers.WorkersScreen
+import com.codeskraps.publicpool.presentation.workers.WorkersContent
 
 internal data object DashboardTab : Tab {
     private fun readResolve(): Any = DashboardTab
@@ -53,7 +53,7 @@ internal data object WorkersTab : Tab {
 
     @Composable
     override fun Content() {
-        WorkersScreen.Content()
+        WorkersContent.Content()
     }
 }
 

+ 19 - 9
app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsContent.kt

@@ -45,20 +45,18 @@ import kotlinx.coroutines.flow.collectLatest
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SettingsContent(screenModel: SettingsScreenModel) {
-    val state by screenModel.state.collectAsState() // Collect state from ScreenModel
+    val state by screenModel.state.collectAsState()
     val context = LocalContext.current
-    val navigator = LocalNavigator.currentOrThrow // Get the navigator
-    val focusManager = LocalFocusManager.current // Focus manager to hide keyboard
+    val navigator = LocalNavigator.currentOrThrow
+    val focusManager = LocalFocusManager.current
 
-    // Track page view when screen becomes visible
     LaunchedEffect(Unit) {
         screenModel.handleEvent(SettingsEvent.OnScreenVisible)
     }
 
-    // Resolve strings needed inside LaunchedEffect here
     val walletSavedMessage = stringResource(R.string.settings_toast_wallet_saved)
+    val baseUrlSavedMessage = stringResource(R.string.settings_toast_base_url_saved)
 
-    // Effect handling (e.g., showing toasts)
     LaunchedEffect(key1 = screenModel.effect) {
         screenModel.effect.collectLatest { effect ->
             when (effect) {
@@ -66,9 +64,11 @@ fun SettingsContent(screenModel: SettingsScreenModel) {
                     Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                 }
                 SettingsEffect.WalletAddressSaved -> {
-                    // Use the pre-resolved string
                     Toast.makeText(context, walletSavedMessage, Toast.LENGTH_SHORT).show()
                 }
+                SettingsEffect.BaseUrlSaved -> {
+                    Toast.makeText(context, baseUrlSavedMessage, Toast.LENGTH_SHORT).show()
+                }
             }
         }
     }
@@ -108,10 +108,20 @@ fun SettingsContent(screenModel: SettingsScreenModel) {
                     singleLine = true
                 )
 
+                // Base URL Section
+                OutlinedTextField(
+                    value = state.baseUrl,
+                    onValueChange = { screenModel.handleEvent(SettingsEvent.BaseUrlChanged(it)) },
+                    label = { Text(stringResource(R.string.settings_label_base_url)) },
+                    modifier = Modifier.fillMaxWidth(),
+                    singleLine = true
+                )
+
+                // Single Save Button for both fields
                 Button(
                     onClick = { 
-                        screenModel.handleEvent(SettingsEvent.SaveWalletAddress)
-                        focusManager.clearFocus() // Clear focus to hide keyboard
+                        screenModel.handleEvent(SettingsEvent.SaveSettings)
+                        focusManager.clearFocus()
                     },
                     modifier = Modifier.align(Alignment.End),
                     colors = ButtonDefaults.buttonColors(

+ 6 - 2
app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsMvi.kt

@@ -7,19 +7,23 @@ import com.codeskraps.publicpool.presentation.common.UiState
 // --- State ---
 data class SettingsState(
     val walletAddress: String = "",
+    val baseUrl: String = "https://public-pool.io:40557/api",
     val isLoading: Boolean = true // Start loading initially
 ) : UiState
 
 // --- Events ---
 sealed interface SettingsEvent : UiEvent {
     data class WalletAddressChanged(val address: String) : SettingsEvent
-    data object SaveWalletAddress : SettingsEvent
-    data object LoadWalletAddress : SettingsEvent // To trigger initial load
+    data class BaseUrlChanged(val url: String) : SettingsEvent
+    data object SaveSettings : SettingsEvent
+    data object LoadWalletAddress : SettingsEvent
+    data object LoadBaseUrl : SettingsEvent
     data object OnScreenVisible : SettingsEvent
 }
 
 // --- Effects ---
 sealed interface SettingsEffect : UiEffect {
     data object WalletAddressSaved : SettingsEffect
+    data object BaseUrlSaved : SettingsEffect
     data class ShowError(val message: String) : SettingsEffect
 } 

+ 34 - 14
app/src/main/java/com/codeskraps/publicpool/presentation/settings/SettingsScreenModel.kt

@@ -6,6 +6,8 @@ import com.codeskraps.publicpool.domain.usecase.GetWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.IdentifyUserUseCase
 import com.codeskraps.publicpool.domain.usecase.SaveWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackPageViewUseCase
+import com.codeskraps.publicpool.domain.usecase.GetBaseUrlUseCase
+import com.codeskraps.publicpool.domain.usecase.SaveBaseUrlUseCase
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.launch
@@ -13,26 +15,32 @@ import kotlinx.coroutines.launch
 class SettingsScreenModel(
     private val getWalletAddressUseCase: GetWalletAddressUseCase,
     private val saveWalletAddressUseCase: SaveWalletAddressUseCase,
+    private val getBaseUrlUseCase: GetBaseUrlUseCase,
+    private val saveBaseUrlUseCase: SaveBaseUrlUseCase,
     private val identifyUserUseCase: IdentifyUserUseCase,
     private val trackPageViewUseCase: TrackPageViewUseCase
-) : StateScreenModel<SettingsState>(SettingsState()) { // Initialize with default state
+) : StateScreenModel<SettingsState>(SettingsState()) {
 
     private val _effect = Channel<SettingsEffect>()
     val effect = _effect.receiveAsFlow()
 
     init {
-        // Trigger loading the address when the ScreenModel is created
+        // Trigger loading when the ScreenModel is created
         handleEvent(SettingsEvent.LoadWalletAddress)
+        handleEvent(SettingsEvent.LoadBaseUrl)
     }
 
     fun handleEvent(event: SettingsEvent) {
         when (event) {
             is SettingsEvent.WalletAddressChanged -> {
-                // Update state directly for text field changes
                 mutableState.update { it.copy(walletAddress = event.address) }
             }
-            SettingsEvent.SaveWalletAddress -> saveWalletAddress()
+            is SettingsEvent.BaseUrlChanged -> {
+                mutableState.update { it.copy(baseUrl = event.url) }
+            }
+            SettingsEvent.SaveSettings -> saveSettings()
             SettingsEvent.LoadWalletAddress -> loadWalletAddress()
+            SettingsEvent.LoadBaseUrl -> loadBaseUrl()
             SettingsEvent.OnScreenVisible -> {
                 screenModelScope.launch {
                     trackPageViewUseCase("Settings")
@@ -46,7 +54,6 @@ class SettingsScreenModel(
             getWalletAddressUseCase()
                 .onStart { mutableState.update { it.copy(isLoading = true) } }
                 .catch { e ->
-                    // Handle error loading address
                     mutableState.update { it.copy(isLoading = false) }
                     _effect.send(SettingsEffect.ShowError("Failed to load wallet address: ${e.message}"))
                 }
@@ -58,26 +65,39 @@ class SettingsScreenModel(
                         )
                     }
                     
-                    // Identify user with the loaded wallet address
                     identifyUserUseCase(address)
                 }
         }
     }
 
-    private fun saveWalletAddress() {
+    private fun loadBaseUrl() {
+        screenModelScope.launch {
+            getBaseUrlUseCase()
+                .catch { e ->
+                    _effect.send(SettingsEffect.ShowError("Failed to load base URL: ${e.message}"))
+                }
+                .collect { url ->
+                    mutableState.update { it.copy(baseUrl = url) }
+                }
+        }
+    }
+
+    private fun saveSettings() {
         screenModelScope.launch {
             try {
-                // Use the current address from the state, explicitly allowing blank addresses
-                val addressToSave = mutableState.value.walletAddress
-                saveWalletAddressUseCase(addressToSave)
+                // Save both wallet address and base URL
+                val currentState = mutableState.value
+                saveWalletAddressUseCase(currentState.walletAddress)
+                saveBaseUrlUseCase(currentState.baseUrl)
                 
-                // Identify user with the newly saved wallet address
-                identifyUserUseCase(addressToSave)
+                // Identify user with the new wallet address
+                identifyUserUseCase(currentState.walletAddress)
                 
+                // Send both success effects
                 _effect.send(SettingsEffect.WalletAddressSaved)
+                _effect.send(SettingsEffect.BaseUrlSaved)
             } catch (e: Exception) {
-                // Handle error saving address
-                _effect.send(SettingsEffect.ShowError("Failed to save wallet address: ${e.message}"))
+                _effect.send(SettingsEffect.ShowError("Failed to save settings: ${e.message}"))
             }
         }
     }

+ 0 - 339
app/src/main/java/com/codeskraps/publicpool/presentation/wallet/WalletScreen.kt

@@ -1,339 +0,0 @@
-package com.codeskraps.publicpool.presentation.wallet
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.os.Parcelable
-import android.widget.Toast
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.pulltorefresh.PullToRefreshBox
-import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import cafe.adriel.voyager.core.screen.Screen
-import cafe.adriel.voyager.koin.koinScreenModel
-import com.codeskraps.publicpool.R
-import com.codeskraps.publicpool.domain.model.CryptoPrice
-import com.codeskraps.publicpool.domain.model.WalletInfo
-import com.codeskraps.publicpool.domain.model.WalletTransaction
-import com.codeskraps.publicpool.presentation.common.AppCard
-import com.codeskraps.publicpool.ui.theme.PositiveGreen
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.parcelize.Parcelize
-import java.text.NumberFormat
-import java.time.format.DateTimeFormatter
-import java.util.Currency
-import java.util.Locale
-
-@Parcelize
-data object WalletScreen : Screen, Parcelable {
-    private fun readResolve(): Any = WalletScreen
-
-    @OptIn(ExperimentalMaterial3Api::class)
-    @Composable
-    override fun Content() {
-        val screenModel: WalletScreenModel = koinScreenModel()
-        val state by screenModel.state.collectAsState()
-        val context = LocalContext.current
-
-        // Track page view when screen becomes visible
-        LaunchedEffect(Unit) {
-            screenModel.onScreenVisible()
-        }
-
-        LaunchedEffect(key1 = screenModel.effect) {
-            screenModel.effect.collectLatest { effect ->
-                when (effect) {
-                    is WalletEffect.ShowError -> {
-                        Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
-                    }
-                }
-            }
-        }
-
-        Scaffold(
-            topBar = { TopAppBar(title = { Text(stringResource(R.string.screen_title_wallet_details)) }) }
-        ) { paddingValues ->
-            PullToRefreshBox(
-                isRefreshing = state.isWalletLoading || state.isLoading,
-                onRefresh = { screenModel.handleEvent(WalletEvent.LoadWalletDetails) },
-                modifier = Modifier
-                    .fillMaxSize()
-                    .padding(
-                        top = paddingValues.calculateTopPadding(),
-                        start = 0.dp,
-                        end = 0.dp,
-                        bottom = 0.dp
-                    )
-            ) {
-                Box(
-                    modifier = Modifier.fillMaxSize()
-                ) {
-                    when {
-                        state.isWalletLoading || state.isLoading -> {
-                            // Combined loading indicator
-                            CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
-                        }
-                        state.walletAddress.isNullOrBlank() -> {
-                            // Use stringResource for message
-                            Text(
-                                text = stringResource(R.string.wallet_error_not_set),
-                                modifier = Modifier.align(Alignment.Center).padding(16.dp),
-                                textAlign = TextAlign.Center
-                            )
-                        }
-                        state.errorMessage != null -> {
-                            // Use stringResource for generic error, keep specific from state
-                            Text(
-                                text = state.errorMessage ?: stringResource(R.string.error_unknown),
-                                modifier = Modifier.align(Alignment.Center).padding(16.dp),
-                                color = MaterialTheme.colorScheme.error,
-                                textAlign = TextAlign.Center
-                            )
-                        }
-                        state.walletInfo == null -> {
-                             // Use stringResource for message
-                             Text(
-                                text = stringResource(R.string.wallet_error_load_failed),
-                                modifier = Modifier.align(Alignment.Center).padding(16.dp),
-                                textAlign = TextAlign.Center
-                            )
-                        }
-                        else -> {
-                            // Display Wallet Info and Transactions
-                            WalletDetailsContent(
-                                walletInfo = state.walletInfo!!,
-                                btcPrice = state.btcPrice
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-@Composable
-fun WalletDetailsContent(
-    walletInfo: WalletInfo,
-    btcPrice: CryptoPrice?
-) {
-    val context = LocalContext.current
-    val btcFormat = remember { "%.8f BTC" }
-    val dateFormatter = remember { DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") }
-    val displayCurrency = stringResource(R.string.currency_usd)
-    val currencyFormat = remember {
-        NumberFormat.getCurrencyInstance(Locale.US).apply {
-            currency = Currency.getInstance(displayCurrency)
-            maximumFractionDigits = 2
-            minimumFractionDigits = 2
-        }
-    }
-
-    val finalBalanceFiat = remember(walletInfo.finalBalanceBtc, btcPrice) {
-        btcPrice?.let { walletInfo.finalBalanceBtc * it.price }
-    }
-
-    LazyColumn(
-        modifier = Modifier.fillMaxSize(),
-        contentPadding = PaddingValues(16.dp),
-        verticalArrangement = Arrangement.spacedBy(16.dp)
-    ) {
-        // Current Price Section
-        item {
-            CurrentPriceCard(btcPrice = btcPrice, currencyFormat = currencyFormat)
-        }
-
-        // Balance Section
-        item {
-            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
-                BalanceCard(
-                    label = stringResource(R.string.wallet_label_final_balance),
-                    valueBtc = btcFormat.format(Locale.US, walletInfo.finalBalanceBtc),
-                    valueFiat = finalBalanceFiat?.let { currencyFormat.format(it) },
-                    fiatCurrencyLabel = btcPrice?.currency ?: stringResource(R.string.currency_usd),
-                    modifier = Modifier.weight(1f)
-                )
-            }
-        }
-
-        // Totals Section
-        item {
-            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
-                BalanceCard(
-                    label = stringResource(R.string.wallet_label_total_received),
-                    valueBtc = btcFormat.format(Locale.US, walletInfo.totalReceivedBtc),
-                    modifier = Modifier.weight(1f)
-                )
-                BalanceCard(
-                    label = stringResource(R.string.wallet_label_total_sent),
-                    valueBtc = btcFormat.format(Locale.US, walletInfo.totalSentBtc),
-                    modifier = Modifier.weight(1f)
-                )
-            }
-        }
-
-        // Transaction Header
-        item {
-            Text(
-                text = stringResource(R.string.wallet_header_recent_transactions_count, walletInfo.transactionCount),
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(top = 8.dp) // Add some space before tx list
-            )
-        }
-
-        // Transaction List
-        items(walletInfo.transactions, key = { it.hash }) { tx ->
-            TransactionItem(tx = tx, dateFormatter = dateFormatter)
-        }
-    }
-}
-
-@Composable
-fun CurrentPriceCard(btcPrice: CryptoPrice?, currencyFormat: NumberFormat) {
-    AppCard(modifier = Modifier.fillMaxWidth()) {
-        Column(
-            modifier = Modifier
-                .fillMaxWidth()
-                .defaultMinSize(minHeight = 90.dp)
-                .padding(12.dp),
-            verticalArrangement = Arrangement.SpaceBetween
-        ) {
-            Text(
-                text = stringResource(R.string.wallet_label_current_btc_price),
-                style = MaterialTheme.typography.labelMedium,
-                color = MaterialTheme.colorScheme.onSurfaceVariant
-            )
-
-            Box(modifier = Modifier.align(Alignment.End)) {
-                if (btcPrice != null) {
-                    Text(
-                        text = currencyFormat.format(btcPrice.price),
-                        style = MaterialTheme.typography.headlineSmall,
-                        fontWeight = FontWeight.Bold,
-                        color = MaterialTheme.colorScheme.onSurface
-                    )
-                } else {
-                    Text(
-                        text = stringResource(R.string.text_placeholder_dash),
-                         style = MaterialTheme.typography.headlineSmall,
-                         fontWeight = FontWeight.Bold,
-                         color = MaterialTheme.colorScheme.onSurfaceVariant
-                     )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-fun BalanceCard(
-    label: String,
-    valueBtc: String,
-    valueFiat: String? = null,
-    fiatCurrencyLabel: String? = null,
-    modifier: Modifier = Modifier
-) {
-    AppCard(modifier = modifier) {
-        Column(
-            modifier = Modifier
-                .fillMaxWidth()
-                .defaultMinSize(minHeight = 90.dp)
-                .padding(12.dp),
-            verticalArrangement = Arrangement.SpaceBetween
-        ) {
-            Text(
-                text = label,
-                style = MaterialTheme.typography.labelMedium,
-                color = MaterialTheme.colorScheme.onSurfaceVariant
-            )
-
-            Box(modifier = Modifier.align(Alignment.End)) {
-                Column(horizontalAlignment = Alignment.End) {
-                    Text(
-                        text = valueBtc,
-                        style = MaterialTheme.typography.bodyLarge,
-                        fontWeight = FontWeight.Bold,
-                        color = MaterialTheme.colorScheme.onSurface
-                    )
-                    Text(
-                        text = valueFiat?.let { "${stringResource(R.string.wallet_balance_fiat_prefix)} $it${fiatCurrencyLabel?.let { c -> " $c" } ?: ""}" } ?: "",
-                        style = MaterialTheme.typography.bodySmall,
-                        color = if (valueFiat != null) PositiveGreen else Color.Transparent
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-fun TransactionItem(tx: WalletTransaction, dateFormatter: DateTimeFormatter) {
-    val btcFormat = remember { "%.8f BTC" }
-    val netValueFormatted = btcFormat.format(Locale.US, tx.resultBtc)
-    val timeFormatted = tx.time?.format(dateFormatter) ?: "Pending"
-    val valueColor = if (tx.resultSatoshis >= 0) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error // Green for positive, Red for negative
-
-    AppCard(modifier = Modifier.fillMaxWidth()) {
-        Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
-            Row(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically
-            ) {
-                Text(text = timeFormatted, style = MaterialTheme.typography.labelMedium)
-                Text(text = netValueFormatted, style = MaterialTheme.typography.bodyMedium, color = valueColor)
-            }
-            Spacer(modifier = Modifier.height(4.dp))
-            Text(
-                text = tx.hash,
-                style = MaterialTheme.typography.bodySmall,
-                maxLines = 1,
-                overflow = TextOverflow.Ellipsis,
-                color = MaterialTheme.colorScheme.onSurfaceVariant
-            )
-        }
-    }
-}
-
-// Helper function to copy text to clipboard
-fun copyToClipboard(context: Context, text: String) {
-    val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
-    // Use context.getString() for non-composable contexts
-    val label = context.getString(R.string.wallet_clipboard_label)
-    val message = context.getString(R.string.wallet_toast_address_copied)
-
-    val clip = ClipData.newPlainText(label, text)
-    clipboard.setPrimaryClip(clip)
-    Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
-} 

+ 2 - 2
app/src/main/java/com/codeskraps/publicpool/presentation/workers/WorkersScreen.kt → app/src/main/java/com/codeskraps/publicpool/presentation/workers/WorkersContent.kt

@@ -57,8 +57,8 @@ import kotlinx.coroutines.launch
 import kotlinx.parcelize.Parcelize
 
 @Parcelize
-data object WorkersScreen : Screen, Parcelable {
-    private fun readResolve(): Any = WorkersScreen
+data object WorkersContent : Screen, Parcelable {
+    private fun readResolve(): Any = WorkersContent
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Composable

+ 2 - 0
app/src/main/res/values-de/strings.xml

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <string name="screen_title_settings">Einstellungen</string>
     <string name="settings_label_wallet_address">Bitcoin-Wallet-Adresse</string>
+    <string name="settings_label_base_url">Basis-URL</string>
     <string name="settings_button_save">Speichern</string>
     <string name="settings_toast_wallet_saved">Wallet-Adresse gespeichert</string>
+    <string name="settings_toast_base_url_saved">Basis-URL gespeichert</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Wallet-Details</string>

+ 2 - 0
app/src/main/res/values-es/strings.xml

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <string name="screen_title_settings">Configuración</string>
     <string name="settings_label_wallet_address">Dirección de billetera Bitcoin</string>
+    <string name="settings_label_base_url">URL base</string>
     <string name="settings_button_save">Guardar</string>
     <string name="settings_toast_wallet_saved">Dirección de billetera guardada</string>
+    <string name="settings_toast_base_url_saved">URL base guardada</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Detalles de la billetera</string>

+ 2 - 0
app/src/main/res/values-fr/strings.xml

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <string name="screen_title_settings">Paramètres</string>
     <string name="settings_label_wallet_address">Adresse du portefeuille Bitcoin</string>
+    <string name="settings_label_base_url">URL de base</string>
     <string name="settings_button_save">Enregistrer</string>
     <string name="settings_toast_wallet_saved">Adresse du portefeuille enregistrée</string>
+    <string name="settings_toast_base_url_saved">URL de base enregistrée</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Détails du portefeuille</string>

+ 2 - 0
app/src/main/res/values-hi/strings.xml

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <string name="screen_title_settings">सेटिंग्स</string>
     <string name="settings_label_wallet_address">बिटकॉइन वॉलेट पता</string>
+    <string name="settings_label_base_url">बेस URL</string>
     <string name="settings_button_save">सहेजें</string>
     <string name="settings_toast_wallet_saved">वॉलेट पता सहेजा गया</string>
+    <string name="settings_toast_base_url_saved">बेस URL सहेजा गया</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">वॉलेट विवरण</string>

+ 2 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <string name="screen_title_settings">设置</string>
     <string name="settings_label_wallet_address">比特币钱包地址</string>
+    <string name="settings_label_base_url">基础 URL</string>
     <string name="settings_button_save">保存</string>
     <string name="settings_toast_wallet_saved">钱包地址已保存</string>
+    <string name="settings_toast_base_url_saved">基础 URL 已保存</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">钱包详情</string>

+ 3 - 1
app/src/main/res/values/strings.xml

@@ -14,9 +14,11 @@
 
     <!-- Settings Screen -->
     <string name="screen_title_settings">Settings</string>
-    <string name="settings_label_wallet_address">Bitcoin Wallet Address</string>
+    <string name="settings_label_wallet_address">Wallet Address</string>
+    <string name="settings_label_base_url">Base URL</string>
     <string name="settings_button_save">Save</string>
     <string name="settings_toast_wallet_saved">Wallet address saved</string>
+    <string name="settings_toast_base_url_saved">Base URL saved</string>
 
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Wallet Details</string>