DashboardScreenModel.kt 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package com.codeskraps.publicpool.presentation.dashboard
  2. import cafe.adriel.voyager.core.model.StateScreenModel
  3. import cafe.adriel.voyager.core.model.screenModelScope
  4. import com.codeskraps.publicpool.domain.model.ChartDataPoint
  5. import com.codeskraps.publicpool.domain.usecase.CalculateTwoHourAverageUseCase
  6. import com.codeskraps.publicpool.domain.usecase.GetChartDataUseCase
  7. import com.codeskraps.publicpool.domain.usecase.GetClientInfoUseCase
  8. import com.codeskraps.publicpool.domain.usecase.GetNetworkInfoUseCase
  9. import com.codeskraps.publicpool.domain.usecase.GetWalletAddressUseCase
  10. import com.codeskraps.publicpool.di.AppReadinessState
  11. import kotlinx.coroutines.Job
  12. import kotlinx.coroutines.channels.Channel
  13. import kotlinx.coroutines.flow.*
  14. import kotlinx.coroutines.launch
  15. class DashboardScreenModel(
  16. private val getWalletAddressUseCase: GetWalletAddressUseCase,
  17. private val getNetworkInfoUseCase: GetNetworkInfoUseCase,
  18. private val getClientInfoUseCase: GetClientInfoUseCase,
  19. private val getChartDataUseCase: GetChartDataUseCase,
  20. private val calculateTwoHourAverageUseCase: CalculateTwoHourAverageUseCase,
  21. private val appReadinessState: AppReadinessState
  22. ) : StateScreenModel<DashboardState>(DashboardState()) {
  23. private val _effect = Channel<DashboardEffect>()
  24. val effect = _effect.receiveAsFlow()
  25. private var dataLoadingJob: Job? = null
  26. init {
  27. // Start loading data immediately
  28. handleEvent(DashboardEvent.LoadData)
  29. }
  30. fun handleEvent(event: DashboardEvent) {
  31. when (event) {
  32. DashboardEvent.LoadData -> loadInitialData()
  33. DashboardEvent.RefreshData -> refreshData()
  34. DashboardEvent.GoToSettings -> sendEffect(DashboardEffect.NavigateToSettings)
  35. // Internal Events triggered by data loading flows/calls
  36. is DashboardEvent.WalletAddressLoaded -> processWalletAddress(event.address)
  37. is DashboardEvent.NetworkInfoResult -> processNetworkInfoResult(event.result)
  38. is DashboardEvent.ClientInfoResult -> processClientInfoResult(event.result)
  39. is DashboardEvent.ChartDataResult -> processChartDataResult(event.result)
  40. }
  41. }
  42. private fun loadInitialData() {
  43. // Collect wallet address changes
  44. screenModelScope.launch {
  45. getWalletAddressUseCase()
  46. .onStart { mutableState.update { it.copy(isWalletLoading = true) } }
  47. .catch { e ->
  48. mutableState.update { it.copy(isWalletLoading = false, errorMessage = "Failed to load wallet address") }
  49. sendEffect(DashboardEffect.ShowErrorSnackbar("Error loading wallet: ${e.message}"))
  50. }
  51. .collect { address ->
  52. handleEvent(DashboardEvent.WalletAddressLoaded(address))
  53. }
  54. }
  55. // Fetch Network Info (doesn't depend on wallet)
  56. fetchNetworkInfo()
  57. }
  58. private fun processWalletAddress(address: String?) {
  59. appReadinessState.setReady()
  60. mutableState.update { it.copy(walletAddress = address, isWalletLoading = false) }
  61. if (address != null && address.isNotBlank()) {
  62. // Wallet address available, fetch client-specific data
  63. fetchClientInfoAndChartData(address)
  64. } else {
  65. // No wallet address, clear client data and show appropriate message/state
  66. mutableState.update {
  67. it.copy(
  68. clientInfo = null,
  69. chartData = emptyList(),
  70. chartDataTwoHourAvg = emptyList(),
  71. isClientInfoLoading = false,
  72. isChartDataLoading = false,
  73. errorMessage = if (!it.isWalletLoading) "Please set a wallet address in Settings" else it.errorMessage
  74. )
  75. }
  76. // Cancel any ongoing client/chart data fetching if wallet becomes null/blank
  77. dataLoadingJob?.cancel()
  78. dataLoadingJob = null
  79. }
  80. }
  81. private fun refreshData() {
  82. dataLoadingJob?.cancel() // Cancel previous jobs if any
  83. mutableState.update { it.copy(errorMessage = null) } // Clear previous errors
  84. fetchNetworkInfo()
  85. state.value.walletAddress?.let { address ->
  86. if (address.isNotBlank()) {
  87. fetchClientInfoAndChartData(address, isRefresh = true)
  88. }
  89. }
  90. }
  91. private fun fetchNetworkInfo() {
  92. screenModelScope.launch {
  93. mutableState.update { it.copy(isNetworkLoading = true) }
  94. val result = getNetworkInfoUseCase()
  95. handleEvent(DashboardEvent.NetworkInfoResult(result))
  96. }
  97. }
  98. private fun fetchClientInfoAndChartData(address: String, isRefresh: Boolean = false) {
  99. dataLoadingJob?.cancel() // Cancel previous loads before starting new ones
  100. dataLoadingJob = screenModelScope.launch {
  101. mutableState.update {
  102. it.copy(
  103. isClientInfoLoading = true,
  104. isChartDataLoading = true,
  105. // Consider clearing previous data on refresh if desired
  106. // clientInfo = if (isRefresh) it.clientInfo else null,
  107. // chartData = if (isRefresh) it.chartData else emptyList(),
  108. // chartDataTwoHourAvg = if (isRefresh) it.chartDataTwoHourAvg else emptyList()
  109. )
  110. }
  111. // Launch both fetches concurrently
  112. launch {
  113. val clientInfoResult = getClientInfoUseCase(address)
  114. handleEvent(DashboardEvent.ClientInfoResult(clientInfoResult))
  115. }
  116. launch {
  117. val chartDataResult = getChartDataUseCase(address)
  118. handleEvent(DashboardEvent.ChartDataResult(chartDataResult))
  119. }
  120. }
  121. }
  122. private fun processNetworkInfoResult(result: Result<com.codeskraps.publicpool.domain.model.NetworkInfo>) {
  123. result.onSuccess {
  124. mutableState.update { s -> s.copy(networkInfo = it, isNetworkLoading = false) }
  125. }.onFailure {
  126. mutableState.update { s -> s.copy(isNetworkLoading = false, errorMessage = "Failed to load network info") }
  127. sendEffect(DashboardEffect.ShowErrorSnackbar("Network Error: ${it.message}"))
  128. }
  129. }
  130. private fun processClientInfoResult(result: Result<com.codeskraps.publicpool.domain.model.ClientInfo>) {
  131. result.onSuccess {
  132. mutableState.update { s -> s.copy(clientInfo = it, isClientInfoLoading = false) }
  133. }.onFailure {
  134. mutableState.update { s -> s.copy(isClientInfoLoading = false, errorMessage = "Failed to load client info") }
  135. sendEffect(DashboardEffect.ShowErrorSnackbar("Client Info Error: ${it.message}"))
  136. }
  137. }
  138. private fun processChartDataResult(result: Result<List<ChartDataPoint>>) {
  139. result.onSuccess {
  140. // Calculate 2-hour average from the fetched 10-min data
  141. val twoHourAvg = calculateTwoHourAverageUseCase(it)
  142. mutableState.update { s ->
  143. s.copy(
  144. chartData = it,
  145. chartDataTwoHourAvg = twoHourAvg,
  146. isChartDataLoading = false
  147. )
  148. }
  149. }.onFailure {
  150. mutableState.update { s -> s.copy(isChartDataLoading = false, errorMessage = "Failed to load chart data") }
  151. sendEffect(DashboardEffect.ShowErrorSnackbar("Chart Data Error: ${it.message}"))
  152. }
  153. }
  154. private fun sendEffect(effectToSend: DashboardEffect) {
  155. screenModelScope.launch {
  156. _effect.send(effectToSend)
  157. }
  158. }
  159. }