diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorMapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorMapper.kt index f4bcb0d3c619..17a7bb55af5c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorMapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorMapper.kt @@ -20,4 +20,6 @@ class SiteMonitorMapper @Inject constructor( fun toNoNetworkError(buttonClick: () -> Unit) = SiteMonitorUiState.NoNetworkError(buttonClick = buttonClick) fun toGenericError(buttonClick: () -> Unit) = SiteMonitorUiState.GenericError(buttonClick = buttonClick) + + fun toLoaded() = SiteMonitorUiState.Loaded(siteMonitorUtils.getUserAgent()) } 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 9a9c52b4920b..65ccbf146e35 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 @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import androidx.activity.compose.setContent +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -38,6 +39,8 @@ class SiteMonitorParentActivity: AppCompatActivity() { private var savedStateSparseArray = SparseArray() private var currentSelectItemId = 0 + private val parentViewModel: SiteMonitorParentViewModel by viewModels() + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -53,6 +56,7 @@ class SiteMonitorParentActivity: AppCompatActivity() { } setContent { AppTheme { + parentViewModel.start() Surface( modifier = Modifier.fillMaxSize(), ) { 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 new file mode 100644 index 000000000000..e0ba0ad2210f --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorParentViewModel.kt @@ -0,0 +1,35 @@ +package org.wordpress.android.ui.sitemonitor + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineDispatcher +import org.wordpress.android.modules.BG_THREAD +import org.wordpress.android.viewmodel.ScopedViewModel +import javax.inject.Inject +import javax.inject.Named + +@HiltViewModel +class SiteMonitorParentViewModel @Inject constructor( + @param:Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher +) : ScopedViewModel(bgDispatcher) { + private val _webViewStates = MutableLiveData>(emptyMap()) + // val webViewStates: LiveData> = _webViewStates + + fun start() { + // todo: understand when the webViewState should be reset + Log.i(javaClass.simpleName, "***=> start: ") + } + + fun saveWebViewState(type: SiteMonitorType, state: SiteMonitorWebViewState) { + Log.i(javaClass.simpleName, "***=> saveWebViewState: $type, $state") + _webViewStates.value = _webViewStates.value?.toMutableMap()?.apply { + put(type, state) + } + } + + fun getWebViewState(type: SiteMonitorType): SiteMonitorWebViewState? { + Log.i(javaClass.simpleName, "***=> getWebViewState: $type") + return _webViewStates.value?.get(type) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabFragment.kt index 8fb61cc8ed03..4433e6adab3d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabFragment.kt @@ -2,6 +2,7 @@ package org.wordpress.android.ui.sitemonitor import android.annotation.SuppressLint import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,6 +30,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.WordPress @@ -38,6 +40,10 @@ import org.wordpress.android.ui.compose.utils.uiStringText @AndroidEntryPoint class SiteMonitorTabFragment : Fragment(), SiteMonitorWebViewClient.SiteMonitorWebViewClientListener { + override fun onCreate(savedInstanceState: Bundle?) { + Log.i(javaClass.simpleName, "***=> onCreate") + super.onCreate(savedInstanceState) + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -48,9 +54,11 @@ class SiteMonitorTabFragment : Fragment(), SiteMonitorWebViewClient.SiteMonitorW } private val viewModel: SiteMonitorTabViewModel by viewModels() + private val parentViewModel: SiteMonitorParentViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + Log.i(javaClass.simpleName, "***=> onViewCreated") initViewModel(getSiteMonitorType(), getUrlTemplate(), getSite()) } @@ -69,7 +77,29 @@ class SiteMonitorTabFragment : Fragment(), SiteMonitorWebViewClient.SiteMonitorW } private fun initViewModel(type: SiteMonitorType, urlTemplate: String, site: SiteModel) { - viewModel.start(type, urlTemplate, site) + Log.i(javaClass.simpleName, "***=> initViewModel") + viewModel.start(type, urlTemplate, site, parentViewModel.getWebViewState(type)!=null) + viewModel.webViewState.observe(viewLifecycleOwner) { webViewState -> + webViewState?.let { + parentViewModel.saveWebViewState(getSiteMonitorType(), it) + } + } + } + + // Custom functions for saving and restoring WebView state + private fun saveWebViewState(webView: WebView): SiteMonitorWebViewState { + val bundle = Bundle() + webView.saveState(bundle) + return SiteMonitorWebViewState(bundle) + } + + private fun restoreWebViewState(webView: WebView, state: SiteMonitorWebViewState) { + try { + webView.restoreState(state.bundle) + } catch (e: Exception) { + // todo: annmarie - track this somewhere + Log.e(javaClass.simpleName, "***=> restoreWebViewState", e) + } } override fun onWebViewPageLoaded(url: String) = viewModel.onUrlLoaded() @@ -100,38 +130,6 @@ class SiteMonitorTabFragment : Fragment(), SiteMonitorWebViewClient.SiteMonitorW } } - @Composable - fun SiteMonitorError(error: SiteMonitorUiState.Error) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier - .padding(20.dp) - .fillMaxWidth() - .fillMaxHeight(), - ) { - androidx.compose.material.Text( - text = uiStringText(uiString = error.title), - style = androidx.compose.material.MaterialTheme.typography.h5, - textAlign = TextAlign.Center - ) - androidx.compose.material.Text( - text = uiStringText(uiString = error.description), - style = androidx.compose.material.MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 8.dp) - ) - if (error.button != null) { - Button( - modifier = Modifier.padding(top = 8.dp), - onClick = error.button.click - ) { - androidx.compose.material.Text(text = uiStringText(uiString = error.button.text)) - } - } - } - } - @SuppressLint("SetJavaScriptEnabled") @Composable private fun SiteMonitorWebView(uiState: SiteMonitorUiState) { @@ -163,10 +161,248 @@ class SiteMonitorTabFragment : Fragment(), SiteMonitorWebViewClient.SiteMonitorW LoadingState() } else { webView?.let { theWebView -> + // todo: annmarie - you may need to move the save here AndroidView( factory = { theWebView }, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + update = { saveWebViewState(theWebView).let { newState -> + Log.i(javaClass.simpleName,"***=> SAVE webview state ") + viewModel.saveWebViewState(newState) + } + } ) + } ?: run { + Log.i(javaClass.simpleName, "***=> Load -> The webview is null") + val currentState = parentViewModel.getWebViewState(getSiteMonitorType()) + currentState?.let { state -> + Log.i(javaClass.simpleName, "***=> RESTORE webview state ") + webView = WebView(requireContext()).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY + settings.userAgentString = (uiState as SiteMonitorUiState.Loaded).userAgent + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + // webViewClient = SiteMonitorWebViewClient(this@SiteMonitorTabFragment) + // todo: annmarie - is this wrong - no this is correct + restoreWebViewState(this, state) + } + + Log.i(javaClass.simpleName, "***=> HOLY FUDGE IS THE webVIEW NULL AGAIN ${webView == null}") + webView?.let { theWebView -> + Log.i(javaClass.simpleName, "***=> Show the webview - restored") + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + AndroidView( + factory = { theWebView }, + modifier = Modifier.fillMaxSize() + ) + } + } + } + } + } + } + } + +// @SuppressLint("SetJavaScriptEnabled") +// @Composable +// private fun SiteMonitorWebView(uiState: SiteMonitorUiState) { +// var webView: WebView? by remember { mutableStateOf(null) } +// +// LaunchedEffect(true) { +// if (uiState is SiteMonitorUiState.Prepared && webView == null) { +// val model = uiState.model +// webView = WebView(requireContext()).apply { +// layoutParams = ViewGroup.LayoutParams( +// ViewGroup.LayoutParams.MATCH_PARENT, +// ViewGroup.LayoutParams.MATCH_PARENT +// ) +// scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY +// settings.userAgentString = model.userAgent +// settings.javaScriptEnabled = true +// settings.domStorageEnabled = true +// webViewClient = SiteMonitorWebViewClient(this@SiteMonitorTabFragment) +// postUrl(WPWebViewActivity.WPCOM_LOGIN_URL, model.addressToLoad.toByteArray()) +// } +// } +// } +// +// DisposableEffect(webView) { +// onDispose { +// // Save the WebView state when it is disposed +// webView?.let { theWebView -> +// saveWebViewState(theWebView).let { newState -> +// Log.i(javaClass.simpleName,"***=> SAVE webview state ") +// viewModel.saveWebViewState(newState) +// } +// } +// } +// } +// +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// if (uiState is SiteMonitorUiState.Prepared) { +// LoadingState() +// } else { +// webView?.let { theWebView -> +// AndroidView( +// factory = { theWebView }, +// modifier = Modifier.fillMaxSize(), +// update = { +// // todo: annmarie - you may need to move the save here +// saveWebViewState(theWebView).let { newState -> +// Log.i(javaClass.simpleName,"***=> SAVE webview state ") +// viewModel.saveWebViewState(newState) +// } +// } +// ) +// } ?: run { +// Log.i(javaClass.simpleName, "***=> Load -> The webview is null") +// val currentState = parentViewModel.getWebViewState(getSiteMonitorType()) +// currentState?.let { state -> +// Log.i(javaClass.simpleName, "***=> RESTORE webview state ") +// webView = WebView(requireContext()).apply { +// layoutParams = ViewGroup.LayoutParams( +// ViewGroup.LayoutParams.MATCH_PARENT, +// ViewGroup.LayoutParams.MATCH_PARENT +// ) +// scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY +// settings.userAgentString = (uiState as SiteMonitorUiState.Loaded).userAgent +// settings.javaScriptEnabled = true +// settings.domStorageEnabled = true +// webViewClient = SiteMonitorWebViewClient(this@SiteMonitorTabFragment) +// // todo: annmarie - is this wrong? +// restoreWebViewState(this, state) +// } +// +// Log.i(javaClass.simpleName, "***=> HOLY FUDGE IS THE webVIEW NULL AGAIN ${webView == null}") +// webView?.let { theWebView -> +// Log.i(javaClass.simpleName, "***=> Show the webview - restored") +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// AndroidView( +// factory = { theWebView }, +// modifier = Modifier.fillMaxSize(), +// update = { +// // todo: annmarie - you may need to move the save here +// saveWebViewState(theWebView).let { newState -> +// Log.i(javaClass.simpleName,"***=> SAVE webview state ") +// viewModel.saveWebViewState(newState) +// } +// } +// ) +// } +// } +// } +// } +// } +// } +// } + + +// @SuppressLint("SetJavaScriptEnabled") +// @Composable +// private fun SiteMonitorWebView(uiState: SiteMonitorUiState) { +// var webView: WebView? by remember { mutableStateOf(null) } +// +// DisposableEffect(webView) { +// onDispose { +// // Save the WebView state when it's disposed +// webView?.let { theWebView -> +// saveWebViewState(theWebView).let { newState -> +// Log.i(javaClass.simpleName, "***=> SAVE webview state ") +// viewModel.saveWebViewState(newState) +// } +// } +// } +// } +// +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// when (uiState) { +// is SiteMonitorUiState.Prepared -> { +// AndroidView( +// factory = { context -> +// WebView(context).apply { +// layoutParams = ViewGroup.LayoutParams( +// ViewGroup.LayoutParams.MATCH_PARENT, +// ViewGroup.LayoutParams.MATCH_PARENT +// ) +// scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY +// settings.userAgentString = uiState.model.userAgent +// settings.javaScriptEnabled = true +// settings.domStorageEnabled = true +// webViewClient = SiteMonitorWebViewClient(this@SiteMonitorTabFragment) +// +// // Restore WebView state if available +// val currentState = parentViewModel.getWebViewState(getSiteMonitorType()) +// currentState?.let { state -> +// Log.i(javaClass.simpleName, "***=> RESTORE webview state ") +// restoreWebViewState(this, state) +// } +// +// // Load URL if the WebView has not been loaded before +// if (webView == null) { +// postUrl(WPWebViewActivity.WPCOM_LOGIN_URL, uiState.model.addressToLoad.toByteArray()) +// } +// +// // Update the reference to the current WebView instance +// webView = this +// } +// }, +// modifier = Modifier.fillMaxSize() +// ) +// } +// is SiteMonitorUiState.Loading -> { +// // Show loading state +// LoadingState() +// } +// else -> { +// // Handle other states as needed +// } +// } +// } +// } + + + @Composable + fun SiteMonitorError(error: SiteMonitorUiState.Error) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .padding(20.dp) + .fillMaxWidth() + .fillMaxHeight(), + ) { + androidx.compose.material.Text( + text = uiStringText(uiString = error.title), + style = androidx.compose.material.MaterialTheme.typography.h5, + textAlign = TextAlign.Center + ) + androidx.compose.material.Text( + text = uiStringText(uiString = error.description), + style = androidx.compose.material.MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 8.dp) + ) + if (error.button != null) { + Button( + modifier = Modifier.padding(top = 8.dp), + onClick = error.button.click + ) { + androidx.compose.material.Text(text = uiStringText(uiString = error.button.text)) } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModel.kt index 275c65087676..083ce73e4889 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorTabViewModel.kt @@ -1,6 +1,9 @@ package org.wordpress.android.ui.sitemonitor import android.text.TextUtils +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow @@ -30,12 +33,24 @@ class SiteMonitorTabViewModel @Inject constructor( private val _uiState = MutableStateFlow(SiteMonitorUiState.Preparing) val uiState: StateFlow = _uiState - fun start(type: SiteMonitorType, urlTemplate: String, site: SiteModel) { + // todo: annmarie - change this to a single live event - eventually, so we don't leak stuff here + private val _webViewState = MutableLiveData(null) + val webViewState: LiveData = _webViewState + + fun start(type: SiteMonitorType, urlTemplate: String, site: SiteModel, hasWebViewState: Boolean = false) { this.siteMonitorType = type this.urlTemplate = urlTemplate this.site = site - loadView() + if (hasWebViewState) { + Log.i(javaClass.simpleName, "***=> start: hasWebViewState is not null") + postUiState(mapper.toLoaded()) + return + } else { + Log.i(javaClass.simpleName, "***=> start: hasWebViewState is null") + loadView() + return + } } private fun loadView() { @@ -104,13 +119,27 @@ class SiteMonitorTabViewModel @Inject constructor( } fun onUrlLoaded() { - postUiState(SiteMonitorUiState.Loaded) + Log.i(javaClass.simpleName, "***=> getWebViewState: onUrlLoaded") + postUiState(mapper.toLoaded()) } fun onWebViewError() { postUiState(mapper.toGenericError(this@SiteMonitorTabViewModel::loadView)) } + fun saveWebViewState(state: SiteMonitorWebViewState) { + Log.i(javaClass.simpleName, "***=> saveWebViewState: $state") + launch { _webViewState.postValue(state) } + } + + // todo: annmarie - need to get the webstate in a single shot - not waiting for a notification and such, unless + // this is a blocking call + fun getWebViewState(): SiteMonitorWebViewState? { + val state = _webViewState.value + Log.i(javaClass.simpleName, "***=> getWebViewState: $state") + return state + } + companion object { const val WPCOM_LOGIN_URL = "https://wordpress.com/wp-login.php" const val WPCOM_DOMAIN = ".wordpress.com" diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorUiState.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorUiState.kt index 8b7dee5a31cc..0f35d47b10a3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorUiState.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorUiState.kt @@ -10,7 +10,7 @@ sealed class SiteMonitorUiState { val model: SiteMonitorModel ) : SiteMonitorUiState() - object Loaded : SiteMonitorUiState() + data class Loaded(val userAgent: String? = null) : SiteMonitorUiState() open class Error( val title: UiString, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorWebViewState.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorWebViewState.kt new file mode 100644 index 000000000000..d50860455ba4 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitemonitor/SiteMonitorWebViewState.kt @@ -0,0 +1,5 @@ +package org.wordpress.android.ui.sitemonitor + +import android.os.Bundle + +data class SiteMonitorWebViewState(val bundle: Bundle)