From bed7304e7ac09a96bbccdcbafc339ec7a602b563 Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 11:22:32 +0530 Subject: [PATCH 1/8] + Adds: the logic to refresh data in viewmodel slice --- .../ui/sitemonitor/SiteMonitorTabViewModelSlice.kt | 9 +++++++++ 1 file changed, 9 insertions(+) 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..425149c514b2 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 @@ -28,6 +28,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,6 +53,12 @@ class SiteMonitorTabViewModelSlice @Inject constructor( assembleAndShowSiteMonitor() } + fun refreshData() { + _isRefreshing.value = true + loadView() + _isRefreshing.value = false + } + private fun checkForInternetConnectivityAndPostErrorIfNeeded() : Boolean { if (networkUtilsWrapper.isNetworkAvailable()) return true postUiState(mapper.toNoNetworkError(::loadView)) From f62071b818a0255244430edbcd5aa3d30d31f0bf Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 11:23:09 +0530 Subject: [PATCH 2/8] + Adds: the refresh data and refresh ui state get function in vm --- .../sitemonitor/SiteMonitorParentViewModel.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) 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() + } + } + } } From 636b0a4f5ef635bfbf66a2de24308c0b2c85f17d Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 11:23:41 +0530 Subject: [PATCH 3/8] + Adds: pull to refresh logic to the view in SiteMonitorParentActivity --- .../sitemonitor/SiteMonitorParentActivity.kt | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) 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 3c015c61d3be..97278d29fc62 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 @@ -292,21 +296,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 { @@ -317,6 +330,12 @@ class SiteMonitorParentActivity : AppCompatActivity(), SiteMonitorWebViewClient. ) } } + PullRefreshIndicator( + refreshing = refreshState.value, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primaryVariant, + ) } } From 7beddb845039529d070c00eade475b023daf62d9 Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 12:20:14 +0530 Subject: [PATCH 4/8] =?UTF-8?q?=E2=86=92=20Moves:=20the=20logic=20of=20pre?= =?UTF-8?q?paring=20address=20to=20background=20dispatcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/sitemonitor/SiteMonitorTabViewModelSlice.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 425149c514b2..881f8d039cd7 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,26 @@ 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.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 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 @@ -77,8 +81,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 { @@ -115,7 +121,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( } fun onUrlLoaded() { - if (uiState.value is SiteMonitorUiState.Prepared){ + if (uiState.value is SiteMonitorUiState.Prepared) { postUiState(SiteMonitorUiState.Loaded) } } From 61c2d9651814b5c3a62c22b991951b51c8bdd24e Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 12:21:08 +0530 Subject: [PATCH 5/8] * Fixes: formatting --- .../android/ui/sitemonitor/SiteMonitorTabViewModelSlice.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 881f8d039cd7..ac55867fb678 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 @@ -32,7 +32,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( private val _uiState = mutableStateOf(SiteMonitorUiState.Preparing) val uiState: State = _uiState - private val _isRefreshing = mutableStateOf(false) + private val _isRefreshing = mutableStateOf(false) val isRefreshing: State = _isRefreshing fun initialize(scope: CoroutineScope) { @@ -63,7 +63,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( _isRefreshing.value = false } - private fun checkForInternetConnectivityAndPostErrorIfNeeded() : Boolean { + private fun checkForInternetConnectivityAndPostErrorIfNeeded(): Boolean { if (networkUtilsWrapper.isNetworkAvailable()) return true postUiState(mapper.toNoNetworkError(::loadView)) return false @@ -110,7 +110,7 @@ class SiteMonitorTabViewModelSlice @Inject constructor( addressToLoad, username, "", - accessToken?:"" + accessToken ?: "" ) } From 45e9be700b7eab674cfdaff54507ffc65b36e701 Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 12:59:13 +0530 Subject: [PATCH 6/8] + Adds: test for refresh logic in viewmodel --- .../SiteMonitorParentViewModelTest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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() + } } From 1859174c8a7d964a2f813fd3608dd219e0cdcd97 Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 13:11:46 +0530 Subject: [PATCH 7/8] =?UTF-8?q?=E2=86=92=20Fixes:=20incorrect=20dependency?= =?UTF-8?q?=20in=20test=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/ui/sitemonitor/SiteMonitorTabViewModelSliceTest.kt | 1 + 1 file changed, 1 insertion(+) 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..830aca21dd4e 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 @@ -34,6 +34,7 @@ class SiteMonitorTabViewModelSliceTest : BaseUnitTest() { @Before fun setUp() = test { viewModel = SiteMonitorTabViewModelSlice( + testDispatcher(), networkUtilsWrapper, accountStore, mapper, From 34b6d4a5d86ec638724dcc2aba27879e1b59e5c0 Mon Sep 17 00:00:00 2001 From: Ajesh R Pai Date: Thu, 1 Feb 2024 15:30:10 +0530 Subject: [PATCH 8/8] + Adds: a delay for the refresh --- .../sitemonitor/SiteMonitorTabViewModelSlice.kt | 15 ++++++++++++--- .../SiteMonitorTabViewModelSliceTest.kt | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) 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 ac55867fb678..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 @@ -6,6 +6,7 @@ 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 @@ -15,6 +16,8 @@ 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, @@ -58,9 +61,15 @@ class SiteMonitorTabViewModelSlice @Inject constructor( } fun refreshData() { - _isRefreshing.value = true - loadView() - _isRefreshing.value = false + 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 { 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 830aca21dd4e..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,6 +31,8 @@ class SiteMonitorTabViewModelSliceTest : BaseUnitTest() { val site = mock() + val refreshStates = mutableListOf() + @Before fun setUp() = test { viewModel = SiteMonitorTabViewModelSlice( @@ -120,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"