diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentActivity.kt index aaf7705baf21..fa171c6ade04 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentActivity.kt @@ -19,9 +19,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.Button +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Surface import androidx.compose.material3.Tab @@ -294,21 +298,30 @@ class SiteMonitorParentActivity : AppCompatActivity(), SiteMonitorWebViewClient. LoadingState() } is SiteMonitorUiState.Loaded -> { - SiteMonitorWebView(webView, modifier) + SiteMonitorWebView(webView, tabType, modifier) } else -> {} } } + @OptIn(ExperimentalMaterialApi::class) @Composable - private fun SiteMonitorWebView(tabWebView: WebView, modifier: Modifier = Modifier) { + private fun SiteMonitorWebView(tabWebView: WebView, tabType: SiteMonitorType, modifier: Modifier = Modifier) { // the webview is retrieved from the activity, so we need to use a mutable variable // to assign to android view - var webView = tabWebView + + val refreshState = siteMonitorParentViewModel.getRefreshState(tabType) + + val pullRefreshState = rememberPullRefreshState( + refreshing = refreshState.value, + onRefresh = { siteMonitorParentViewModel.refreshData(tabType) } + ) + Box( modifier = modifier .fillMaxSize() + .pullRefresh(pullRefreshState) ) { LazyColumn(modifier = Modifier.fillMaxHeight()) { item { @@ -319,6 +332,12 @@ class SiteMonitorParentActivity : AppCompatActivity(), SiteMonitorWebViewClient. ) } } + PullRefreshIndicator( + refreshing = refreshState.value, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primaryVariant, + ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModel.kt index 2a18f07e1fb2..104800214320 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModel.kt @@ -92,4 +92,36 @@ class SiteMonitorParentViewModel @Inject constructor( phpLogViewModel.onCleared() webServerViewModel.onCleared() } + + fun getRefreshState(siteMonitorType: SiteMonitorType): State { + return when (siteMonitorType) { + SiteMonitorType.METRICS -> { + metricsViewModel.isRefreshing + } + + SiteMonitorType.PHP_LOGS -> { + phpLogViewModel.isRefreshing + } + + SiteMonitorType.WEB_SERVER_LOGS -> { + webServerViewModel.isRefreshing + } + } + } + + fun refreshData(siteMonitorType: SiteMonitorType) { + when(siteMonitorType) { + SiteMonitorType.METRICS -> { + metricsViewModel.refreshData() + } + + SiteMonitorType.PHP_LOGS -> { + phpLogViewModel.refreshData() + } + + SiteMonitorType.WEB_SERVER_LOGS -> { + webServerViewModel.refreshData() + } + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSlice.kt index 45ec9a56d76f..91dea1294b59 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSlice.kt @@ -3,22 +3,29 @@ package org.wordpress.android.ui.sitemonitor import android.text.TextUtils import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.fluxc.store.SiteStore +import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.util.NetworkUtilsWrapper import javax.inject.Inject +import javax.inject.Named + +const val REFRESH_DELAY = 500L class SiteMonitorTabViewModelSlice @Inject constructor( + @param:Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, private val networkUtilsWrapper: NetworkUtilsWrapper, private val accountStore: AccountStore, private val mapper: SiteMonitorMapper, private val siteMonitorUtils: SiteMonitorUtils, private val siteStore: SiteStore, -){ +) { private lateinit var scope: CoroutineScope private lateinit var site: SiteModel @@ -28,6 +35,9 @@ class SiteMonitorTabViewModelSlice @Inject constructor( private val _uiState = mutableStateOf(SiteMonitorUiState.Preparing) val uiState: State = _uiState + private val _isRefreshing = mutableStateOf(false) + val isRefreshing: State = _isRefreshing + fun initialize(scope: CoroutineScope) { this.scope = scope } @@ -50,7 +60,19 @@ class SiteMonitorTabViewModelSlice @Inject constructor( assembleAndShowSiteMonitor() } - private fun checkForInternetConnectivityAndPostErrorIfNeeded() : Boolean { + fun refreshData() { + scope.launch { + _isRefreshing.value = true + // this delay is to prevent the refresh from being too fast + // so that the user can see the refresh animation + // also this would fix the unit tests + delay(REFRESH_DELAY) + loadView() + _isRefreshing.value = false + } + } + + private fun checkForInternetConnectivityAndPostErrorIfNeeded(): Boolean { if (networkUtilsWrapper.isNetworkAvailable()) return true postUiState(mapper.toNoNetworkError(::loadView)) return false @@ -68,8 +90,10 @@ class SiteMonitorTabViewModelSlice @Inject constructor( val sanitizedUrl = siteMonitorUtils.sanitizeSiteUrl(site.url) val url = urlTemplate.replace("{blog}", sanitizedUrl) - val addressToLoad = prepareAddressToLoad(url) - postUiState(mapper.toPrepared(url, addressToLoad, siteMonitorType)) + scope.launch(bgDispatcher) { + val addressToLoad = prepareAddressToLoad(url) + postUiState(mapper.toPrepared(url, addressToLoad, siteMonitorType)) + } } private fun prepareAddressToLoad(url: String): String { @@ -95,7 +119,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( addressToLoad, username, "", - accessToken?:"" + accessToken ?: "" ) } @@ -106,7 +130,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( } fun onUrlLoaded() { - if (uiState.value is SiteMonitorUiState.Prepared){ + if (uiState.value is SiteMonitorUiState.Prepared) { postUiState(SiteMonitorUiState.Loaded) } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModelTest.kt index 411f72a9a442..98d3ddf0e3a1 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModelTest.kt @@ -167,4 +167,25 @@ class SiteMonitorParentViewModelTest: BaseUnitTest(){ verify(webServerViewModel).onWebViewError() } + + @Test + fun `given metrics, when refresh is invoked, then metric vm slice refresh is invoked`() { + viewModel.refreshData(SiteMonitorType.METRICS) + + verify(metricsViewModel).refreshData() + } + + @Test + fun `given php logs, when refresh is invoked, then php logs vm slice refresh is invoked`() { + viewModel.refreshData(SiteMonitorType.PHP_LOGS) + + verify(phpLogViewModel).refreshData() + } + + @Test + fun `given webserver logs, when refresh is invoked, then webserver vm slice refresh is invoked`() { + viewModel.refreshData(SiteMonitorType.WEB_SERVER_LOGS) + + verify(webServerViewModel).refreshData() + } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSliceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSliceTest.kt index 3fe113c67fb7..02eb59f3a157 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSliceTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModelSliceTest.kt @@ -31,9 +31,12 @@ class SiteMonitorTabViewModelSliceTest : BaseUnitTest() { val site = mock() + val refreshStates = mutableListOf() + @Before fun setUp() = test { viewModel = SiteMonitorTabViewModelSlice( + testDispatcher(), networkUtilsWrapper, accountStore, mapper, @@ -119,6 +122,19 @@ class SiteMonitorTabViewModelSliceTest : BaseUnitTest() { assertThat(viewModel.uiState.value).isInstanceOf(SiteMonitorUiState.GenericError::class.java) } + @Test + fun `given loaded state, when refresh is invoked, then uiState loaded is posted`() = test { + viewModel.start(SiteMonitorType.METRICS, SiteMonitorTabItem.Metrics.urlTemplate, site) + advanceUntilIdle() + viewModel.onUrlLoaded() + viewModel.refreshData() + + assertThat(viewModel.isRefreshing.value).isTrue() + advanceUntilIdle() + assertThat(viewModel.uiState.value).isInstanceOf(SiteMonitorUiState.Prepared::class.java) + assertThat(viewModel.isRefreshing.value).isFalse() + } + companion object { const val USER_NAME = "user_name" const val ACCESS_TOKEN = "access_token"