浏览代码

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.

codeskraps 1 周之前
父节点
当前提交
95fde76e49
共有 20 个文件被更改,包括 152 次插入382 次删除
  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 {
 object PreferencesKeys {
     val WALLET_ADDRESS = stringPreferencesKey("wallet_address")
     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
     // We can add implementations here or in a separate class
     companion object {
     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)
 // 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 {
     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 {
     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> {
     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
     // 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) {
        } catch(e: Exception) {
             Log.e(TAG, "Failed to save wallet address to DataStore", e)
             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 ---
     // --- Blockchain.info Data ---
     override suspend fun getBlockchainWalletInfo(walletAddress: String): Result<WalletInfo> {
     override suspend fun getBlockchainWalletInfo(walletAddress: String): Result<WalletInfo> {
         return try {
         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.core.module.dsl.singleOf
 import org.koin.dsl.bind
 import org.koin.dsl.bind
 import org.koin.dsl.module
 import org.koin.dsl.module
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 
 
 val dataModule = module {
 val dataModule = module {
 
 
@@ -46,7 +48,14 @@ val dataModule = module {
     }
     }
 
 
     // API Service Implementation
     // API Service Implementation
-    singleOf(::KtorApiServiceImpl) bind KtorApiService::class
+    single<KtorApiService> {
+        KtorApiServiceImpl(
+            client = get(),
+            baseUrlProvider = {
+                runBlocking { get<PublicPoolRepository>().getBaseUrl().first() }
+            }
+        )
+    }
 
 
     // Repository Implementation
     // Repository Implementation
     singleOf(::PublicPoolRepositoryImpl) bind PublicPoolRepository::class
     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
 package com.codeskraps.publicpool.di
 
 
 import com.codeskraps.publicpool.domain.usecase.CalculateTwoHourAverageUseCase
 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.GetBlockchainWalletInfoUseCase
 import com.codeskraps.publicpool.domain.usecase.GetBtcPriceUseCase
 import com.codeskraps.publicpool.domain.usecase.GetBtcPriceUseCase
 import com.codeskraps.publicpool.domain.usecase.GetChartDataUseCase
 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.GetWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.IdentifyUserUseCase
 import com.codeskraps.publicpool.domain.usecase.IdentifyUserUseCase
 import com.codeskraps.publicpool.domain.usecase.InitializeAnalyticsUseCase
 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.SaveWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackEventUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackEventUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackPageViewUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackPageViewUseCase
@@ -19,6 +21,8 @@ val domainModule = module {
     // Use Case providers
     // Use Case providers
     factoryOf(::GetWalletAddressUseCase)
     factoryOf(::GetWalletAddressUseCase)
     factoryOf(::SaveWalletAddressUseCase)
     factoryOf(::SaveWalletAddressUseCase)
+    factoryOf(::GetBaseUrlUseCase)
+    factoryOf(::SaveBaseUrlUseCase)
     factoryOf(::GetNetworkInfoUseCase)
     factoryOf(::GetNetworkInfoUseCase)
     factoryOf(::GetClientInfoUseCase)
     factoryOf(::GetClientInfoUseCase)
     factoryOf(::GetChartDataUseCase)
     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
     fun getWalletAddress(): Flow<String?> // Flow to observe changes
     suspend fun saveWalletAddress(address: String)
     suspend fun saveWalletAddress(address: String)
 
 
+    // --- Base URL Management ---
+    fun getBaseUrl(): Flow<String>
+    suspend fun saveBaseUrl(url: String)
+
     // --- Blockchain.info Data ---
     // --- Blockchain.info Data ---
     suspend fun getBlockchainWalletInfo(walletAddress: String): Result<WalletInfo>
     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 cafe.adriel.voyager.navigator.tab.TabOptions
 import com.codeskraps.publicpool.R
 import com.codeskraps.publicpool.R
 import com.codeskraps.publicpool.presentation.wallet.WalletContent
 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 {
 internal data object DashboardTab : Tab {
     private fun readResolve(): Any = DashboardTab
     private fun readResolve(): Any = DashboardTab
@@ -53,7 +53,7 @@ internal data object WorkersTab : Tab {
 
 
     @Composable
     @Composable
     override fun Content() {
     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)
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 @Composable
 fun SettingsContent(screenModel: SettingsScreenModel) {
 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 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) {
     LaunchedEffect(Unit) {
         screenModel.handleEvent(SettingsEvent.OnScreenVisible)
         screenModel.handleEvent(SettingsEvent.OnScreenVisible)
     }
     }
 
 
-    // Resolve strings needed inside LaunchedEffect here
     val walletSavedMessage = stringResource(R.string.settings_toast_wallet_saved)
     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) {
     LaunchedEffect(key1 = screenModel.effect) {
         screenModel.effect.collectLatest { effect ->
         screenModel.effect.collectLatest { effect ->
             when (effect) {
             when (effect) {
@@ -66,9 +64,11 @@ fun SettingsContent(screenModel: SettingsScreenModel) {
                     Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                     Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                 }
                 }
                 SettingsEffect.WalletAddressSaved -> {
                 SettingsEffect.WalletAddressSaved -> {
-                    // Use the pre-resolved string
                     Toast.makeText(context, walletSavedMessage, Toast.LENGTH_SHORT).show()
                     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
                     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(
                 Button(
                     onClick = { 
                     onClick = { 
-                        screenModel.handleEvent(SettingsEvent.SaveWalletAddress)
-                        focusManager.clearFocus() // Clear focus to hide keyboard
+                        screenModel.handleEvent(SettingsEvent.SaveSettings)
+                        focusManager.clearFocus()
                     },
                     },
                     modifier = Modifier.align(Alignment.End),
                     modifier = Modifier.align(Alignment.End),
                     colors = ButtonDefaults.buttonColors(
                     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 ---
 // --- State ---
 data class SettingsState(
 data class SettingsState(
     val walletAddress: String = "",
     val walletAddress: String = "",
+    val baseUrl: String = "https://public-pool.io:40557/api",
     val isLoading: Boolean = true // Start loading initially
     val isLoading: Boolean = true // Start loading initially
 ) : UiState
 ) : UiState
 
 
 // --- Events ---
 // --- Events ---
 sealed interface SettingsEvent : UiEvent {
 sealed interface SettingsEvent : UiEvent {
     data class WalletAddressChanged(val address: String) : SettingsEvent
     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
     data object OnScreenVisible : SettingsEvent
 }
 }
 
 
 // --- Effects ---
 // --- Effects ---
 sealed interface SettingsEffect : UiEffect {
 sealed interface SettingsEffect : UiEffect {
     data object WalletAddressSaved : SettingsEffect
     data object WalletAddressSaved : SettingsEffect
+    data object BaseUrlSaved : SettingsEffect
     data class ShowError(val message: String) : 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.IdentifyUserUseCase
 import com.codeskraps.publicpool.domain.usecase.SaveWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.SaveWalletAddressUseCase
 import com.codeskraps.publicpool.domain.usecase.TrackPageViewUseCase
 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.channels.Channel
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
@@ -13,26 +15,32 @@ import kotlinx.coroutines.launch
 class SettingsScreenModel(
 class SettingsScreenModel(
     private val getWalletAddressUseCase: GetWalletAddressUseCase,
     private val getWalletAddressUseCase: GetWalletAddressUseCase,
     private val saveWalletAddressUseCase: SaveWalletAddressUseCase,
     private val saveWalletAddressUseCase: SaveWalletAddressUseCase,
+    private val getBaseUrlUseCase: GetBaseUrlUseCase,
+    private val saveBaseUrlUseCase: SaveBaseUrlUseCase,
     private val identifyUserUseCase: IdentifyUserUseCase,
     private val identifyUserUseCase: IdentifyUserUseCase,
     private val trackPageViewUseCase: TrackPageViewUseCase
     private val trackPageViewUseCase: TrackPageViewUseCase
-) : StateScreenModel<SettingsState>(SettingsState()) { // Initialize with default state
+) : StateScreenModel<SettingsState>(SettingsState()) {
 
 
     private val _effect = Channel<SettingsEffect>()
     private val _effect = Channel<SettingsEffect>()
     val effect = _effect.receiveAsFlow()
     val effect = _effect.receiveAsFlow()
 
 
     init {
     init {
-        // Trigger loading the address when the ScreenModel is created
+        // Trigger loading when the ScreenModel is created
         handleEvent(SettingsEvent.LoadWalletAddress)
         handleEvent(SettingsEvent.LoadWalletAddress)
+        handleEvent(SettingsEvent.LoadBaseUrl)
     }
     }
 
 
     fun handleEvent(event: SettingsEvent) {
     fun handleEvent(event: SettingsEvent) {
         when (event) {
         when (event) {
             is SettingsEvent.WalletAddressChanged -> {
             is SettingsEvent.WalletAddressChanged -> {
-                // Update state directly for text field changes
                 mutableState.update { it.copy(walletAddress = event.address) }
                 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.LoadWalletAddress -> loadWalletAddress()
+            SettingsEvent.LoadBaseUrl -> loadBaseUrl()
             SettingsEvent.OnScreenVisible -> {
             SettingsEvent.OnScreenVisible -> {
                 screenModelScope.launch {
                 screenModelScope.launch {
                     trackPageViewUseCase("Settings")
                     trackPageViewUseCase("Settings")
@@ -46,7 +54,6 @@ class SettingsScreenModel(
             getWalletAddressUseCase()
             getWalletAddressUseCase()
                 .onStart { mutableState.update { it.copy(isLoading = true) } }
                 .onStart { mutableState.update { it.copy(isLoading = true) } }
                 .catch { e ->
                 .catch { e ->
-                    // Handle error loading address
                     mutableState.update { it.copy(isLoading = false) }
                     mutableState.update { it.copy(isLoading = false) }
                     _effect.send(SettingsEffect.ShowError("Failed to load wallet address: ${e.message}"))
                     _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)
                     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 {
         screenModelScope.launch {
             try {
             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.WalletAddressSaved)
+                _effect.send(SettingsEffect.BaseUrlSaved)
             } catch (e: Exception) {
             } 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
 import kotlinx.parcelize.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)
     @OptIn(ExperimentalMaterial3Api::class)
     @Composable
     @Composable

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

@@ -15,8 +15,10 @@
     <!-- Settings Screen -->
     <!-- Settings Screen -->
     <string name="screen_title_settings">Einstellungen</string>
     <string name="screen_title_settings">Einstellungen</string>
     <string name="settings_label_wallet_address">Bitcoin-Wallet-Adresse</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_button_save">Speichern</string>
     <string name="settings_toast_wallet_saved">Wallet-Adresse gespeichert</string>
     <string name="settings_toast_wallet_saved">Wallet-Adresse gespeichert</string>
+    <string name="settings_toast_base_url_saved">Basis-URL gespeichert</string>
 
 
     <!-- Wallet Screen -->
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Wallet-Details</string>
     <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 -->
     <!-- Settings Screen -->
     <string name="screen_title_settings">Configuración</string>
     <string name="screen_title_settings">Configuración</string>
     <string name="settings_label_wallet_address">Dirección de billetera Bitcoin</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_button_save">Guardar</string>
     <string name="settings_toast_wallet_saved">Dirección de billetera guardada</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 -->
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Detalles de la billetera</string>
     <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 -->
     <!-- Settings Screen -->
     <string name="screen_title_settings">Paramètres</string>
     <string name="screen_title_settings">Paramètres</string>
     <string name="settings_label_wallet_address">Adresse du portefeuille Bitcoin</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_button_save">Enregistrer</string>
     <string name="settings_toast_wallet_saved">Adresse du portefeuille enregistrée</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 -->
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Détails du portefeuille</string>
     <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 -->
     <!-- Settings Screen -->
     <string name="screen_title_settings">सेटिंग्स</string>
     <string name="screen_title_settings">सेटिंग्स</string>
     <string name="settings_label_wallet_address">बिटकॉइन वॉलेट पता</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_button_save">सहेजें</string>
     <string name="settings_toast_wallet_saved">वॉलेट पता सहेजा गया</string>
     <string name="settings_toast_wallet_saved">वॉलेट पता सहेजा गया</string>
+    <string name="settings_toast_base_url_saved">बेस URL सहेजा गया</string>
 
 
     <!-- Wallet Screen -->
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">वॉलेट विवरण</string>
     <string name="screen_title_wallet_details">वॉलेट विवरण</string>

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

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

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

@@ -14,9 +14,11 @@
 
 
     <!-- Settings Screen -->
     <!-- Settings Screen -->
     <string name="screen_title_settings">Settings</string>
     <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_button_save">Save</string>
     <string name="settings_toast_wallet_saved">Wallet address saved</string>
     <string name="settings_toast_wallet_saved">Wallet address saved</string>
+    <string name="settings_toast_base_url_saved">Base URL saved</string>
 
 
     <!-- Wallet Screen -->
     <!-- Wallet Screen -->
     <string name="screen_title_wallet_details">Wallet Details</string>
     <string name="screen_title_wallet_details">Wallet Details</string>