Skip to content

Commit acb1161

Browse files
adalpariCopilot
andauthored
Show the site selector and set up my site screen after the application password login (#21968)
* Re adding the site store calls * Making it work with XML-RPC * Properly saving the site * Adding toasts * Adding tests * detekt * Removing debug code * Update libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/SiteStore.kt Co-authored-by: Copilot <[email protected]> * typos * Listen for when the site is fetched * Fetching profile * Correctly handling the url * Opening login epilogue * detekt * Adding tests * detekt * Removing debug code * Some fixes * Fixing tests * Adding ProgressIndicator * Clear flags for MainActivity * Using the already existing AppPrefsWrapper * Using AppLogWrapper for loiging --------- Co-authored-by: Copilot <[email protected]>
1 parent 293eafc commit acb1161

File tree

4 files changed

+430
-109
lines changed

4 files changed

+430
-109
lines changed

WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginActivity.kt

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@ package org.wordpress.android.ui.accounts.applicationpassword
22

33
import android.content.Intent
44
import android.os.Bundle
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.material3.CircularProgressIndicator
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.Modifier
510
import androidx.lifecycle.ViewModelProvider
611
import androidx.lifecycle.lifecycleScope
712
import dagger.hilt.android.AndroidEntryPoint
813
import kotlinx.coroutines.flow.launchIn
914
import kotlinx.coroutines.flow.onEach
1015
import org.wordpress.android.R
11-
import org.wordpress.android.util.ToastUtils
16+
import org.wordpress.android.ui.ActivityLauncher
1217
import org.wordpress.android.ui.main.BaseAppCompatActivity
1318
import org.wordpress.android.ui.main.WPMainActivity
19+
import org.wordpress.android.util.ToastUtils
20+
import org.wordpress.android.util.extensions.setContent
1421
import javax.inject.Inject
1522

1623
@AndroidEntryPoint
@@ -23,6 +30,14 @@ class ApplicationPasswordLoginActivity: BaseAppCompatActivity() {
2330
override fun onCreate(savedInstanceState: Bundle?) {
2431
super.onCreate(savedInstanceState)
2532
initViewModel()
33+
setContent {
34+
Box(
35+
contentAlignment = Alignment.Center,
36+
modifier = Modifier.fillMaxSize()
37+
) {
38+
CircularProgressIndicator()
39+
}
40+
}
2641
}
2742

2843
private fun initViewModel() {
@@ -31,13 +46,13 @@ class ApplicationPasswordLoginActivity: BaseAppCompatActivity() {
3146
viewModel!!.setupSite(intent.dataString.orEmpty())
3247
}
3348

34-
private fun openMainActivity(siteUrl: String?) {
35-
if (siteUrl != null) {
49+
private fun openMainActivity(navigationActionData: ApplicationPasswordLoginViewModel.NavigationActionData) {
50+
if (!navigationActionData.isError && navigationActionData.siteUrl != null) {
3651
ToastUtils.showToast(
3752
this,
3853
getString(
3954
R.string.application_password_credentials_stored,
40-
siteUrl
55+
navigationActionData.siteUrl
4156
)
4257
)
4358
intent.setData(null)
@@ -46,17 +61,35 @@ class ApplicationPasswordLoginActivity: BaseAppCompatActivity() {
4661
this,
4762
getString(
4863
R.string.application_password_credentials_storing_error,
49-
siteUrl
64+
navigationActionData.siteUrl
5065
)
5166
)
5267
}
53-
val mainActivityIntent =
54-
Intent(this, WPMainActivity::class.java)
55-
mainActivityIntent.setFlags(
56-
(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
57-
or Intent.FLAG_ACTIVITY_CLEAR_TASK)
58-
)
59-
startActivity(mainActivityIntent)
68+
69+
if (navigationActionData.isError) {
70+
ActivityLauncher.showMainActivity(this)
71+
} else if (navigationActionData.showSiteSelector) {
72+
ActivityLauncher.showMainActivityAndLoginEpilogue(this, navigationActionData.oldSitesIDs, false)
73+
} else if (navigationActionData.showPostSignupInterstitial) {
74+
ActivityLauncher.showPostSignupInterstitial(this)
75+
} else {
76+
val mainActivityIntent = Intent(this, WPMainActivity::class.java)
77+
mainActivityIntent.setFlags(
78+
(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
79+
or Intent.FLAG_ACTIVITY_CLEAR_TASK)
80+
)
81+
startActivity(mainActivityIntent)
82+
}
6083
finish()
6184
}
85+
86+
override fun onStart() {
87+
super.onStart()
88+
viewModel?.onStart()
89+
}
90+
91+
override fun onStop() {
92+
super.onStop()
93+
viewModel?.onStop()
94+
}
6295
}
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,132 @@
11
package org.wordpress.android.ui.accounts.applicationpassword
22

3-
import android.util.Log
43
import androidx.lifecycle.ViewModel
54
import androidx.lifecycle.viewModelScope
65
import kotlinx.coroutines.CoroutineDispatcher
76
import kotlinx.coroutines.flow.MutableSharedFlow
87
import kotlinx.coroutines.flow.asSharedFlow
98
import kotlinx.coroutines.launch
109
import kotlinx.coroutines.withContext
10+
import org.greenrobot.eventbus.Subscribe
11+
import org.greenrobot.eventbus.ThreadMode
12+
import org.wordpress.android.fluxc.Dispatcher
1113
import org.wordpress.android.fluxc.generated.SiteActionBuilder
1214
import org.wordpress.android.fluxc.network.discovery.SelfHostedEndpointFinder
13-
import org.wordpress.android.fluxc.store.SiteStore.RefreshSitesXMLRPCPayload
1415
import org.wordpress.android.fluxc.store.SiteStore
16+
import org.wordpress.android.fluxc.store.SiteStore.OnProfileFetched
17+
import org.wordpress.android.fluxc.store.SiteStore.OnSiteChanged
18+
import org.wordpress.android.fluxc.store.SiteStore.RefreshSitesXMLRPCPayload
19+
import org.wordpress.android.fluxc.utils.AppLogWrapper
20+
import org.wordpress.android.login.util.SiteUtils
1521
import org.wordpress.android.modules.IO_THREAD
1622
import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper
17-
23+
import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin
24+
import org.wordpress.android.ui.prefs.AppPrefsWrapper
25+
import org.wordpress.android.util.AppLog
26+
import org.wordpress.android.util.UrlUtils
1827
import javax.inject.Inject
1928
import javax.inject.Named
2029

21-
private const val TAG = "ApplicationPasswordLoginViewModel"
22-
2330
class ApplicationPasswordLoginViewModel @Inject constructor(
2431
@Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher,
32+
// Dispatcher is the way to dispatch actions to Flux. It will call siteStore.onAction()
33+
private val dispatcher: Dispatcher,
2534
private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper,
2635
private val selfHostedEndpointFinder: SelfHostedEndpointFinder,
2736
private val siteStore: SiteStore,
37+
private val appPrefsWrapper: AppPrefsWrapper,
38+
private val appLogWrapper: AppLogWrapper,
2839
) : ViewModel() {
29-
private val _onFinishedEvent = MutableSharedFlow<String?>()
40+
private val _onFinishedEvent = MutableSharedFlow<NavigationActionData>()
3041
/**
3142
* A shared flow that emits the site URL when the setup is finished.
3243
* It can emit null if the site could not be set up.
3344
*/
3445
val onFinishedEvent = _onFinishedEvent.asSharedFlow()
3546

47+
private var currentUrlLogin: UriLogin? = null
48+
private var oldSitesIDs: ArrayList<Int>? = null
49+
50+
fun onStart() {
51+
dispatcher.register(this)
52+
oldSitesIDs = SiteUtils.getCurrentSiteIds(siteStore, false)
53+
}
54+
55+
fun onStop() {
56+
dispatcher.unregister(this)
57+
}
58+
3659
/**
3760
* This method is called to set up the site with the provided raw data.
3861
*
3962
* @param rawData The raw data containing the callback data from the application password login.
4063
*/
4164
fun setupSite(rawData: String) {
4265
viewModelScope.launch {
66+
if (rawData.isEmpty()) {
67+
appLogWrapper.e(AppLog.T.MAIN, "Cannot store credentials: rawData is empty")
68+
_onFinishedEvent.emit(
69+
NavigationActionData(
70+
showSiteSelector = false,
71+
showPostSignupInterstitial = false,
72+
siteUrl = "",
73+
oldSitesIDs = oldSitesIDs,
74+
isError = true
75+
)
76+
)
77+
return@launch
78+
}
4379
val urlLogin = applicationPasswordLoginHelper.getSiteUrlLoginFromRawData(rawData)
4480
// Store credentials if the site already exists
4581
val credentialsStored = storeCredentials(rawData)
82+
// If the site already exists, we can skip fetching it again
4683
if (credentialsStored) {
47-
_onFinishedEvent.emit(urlLogin.siteUrl)
84+
_onFinishedEvent.emit(
85+
NavigationActionData(
86+
showSiteSelector = false,
87+
showPostSignupInterstitial = false,
88+
siteUrl = urlLogin.siteUrl,
89+
oldSitesIDs = oldSitesIDs,
90+
isError = false
91+
)
92+
)
93+
} else {
94+
fetchSites(urlLogin)
95+
currentUrlLogin = urlLogin
96+
}
97+
}
98+
}
99+
100+
@Suppress("TooGenericExceptionCaught")
101+
private suspend fun storeCredentials(rawData: String): Boolean = withContext(ioDispatcher) {
102+
try {
103+
if (rawData.isEmpty()) {
104+
appLogWrapper.e(AppLog.T.DB, "Cannot store credentials: rawData is empty")
105+
false
48106
} else {
49-
val siteFetched = fetchSites(urlLogin)
50-
_onFinishedEvent.emit(if (siteFetched) urlLogin.siteUrl else null)
107+
val credentialsStored = applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom(rawData)
108+
credentialsStored
51109
}
110+
} catch (e: Exception) {
111+
appLogWrapper.e(AppLog.T.DB, "Error storing credentials: ${e.stackTrace}")
112+
false
52113
}
53114
}
54115

55116
@Suppress("TooGenericExceptionCaught")
56117
private suspend fun fetchSites(
57-
urlLogin: ApplicationPasswordLoginHelper.UriLogin
58-
): Boolean = withContext(ioDispatcher) {
118+
urlLogin: UriLogin
119+
) = withContext(ioDispatcher) {
59120
try {
60121
if (urlLogin.user.isNullOrEmpty() ||
61122
urlLogin.password.isNullOrEmpty() ||
62123
urlLogin.siteUrl.isNullOrEmpty()) {
63-
Log.e(TAG, "Cannot store credentials: rawData is empty")
64-
false
124+
appLogWrapper.e(AppLog.T.MAIN, "Cannot store credentials: rawData is empty")
125+
emitErrorFetching(urlLogin)
65126
} else {
66127
val xmlRpcEndpoint =
67128
selfHostedEndpointFinder.verifyOrDiscoverXMLRPCEndpoint(urlLogin.siteUrl)
68-
siteStore.onAction(
129+
dispatcher.dispatch(
69130
SiteActionBuilder.newFetchSitesXmlRpcFromApplicationPasswordAction(
70131
RefreshSitesXMLRPCPayload(
71132
username = urlLogin.user,
@@ -74,27 +135,68 @@ class ApplicationPasswordLoginViewModel @Inject constructor(
74135
)
75136
)
76137
)
77-
true
78138
}
79139
} catch (e: Exception) {
80-
Log.e(TAG, "Error storing credentials", e)
81-
false
140+
appLogWrapper.e(AppLog.T.API, "Error storing credentials: ${e.stackTrace}")
141+
emitErrorFetching(urlLogin)
82142
}
83143
}
84144

85-
@Suppress("TooGenericExceptionCaught")
86-
private suspend fun storeCredentials(rawData: String): Boolean = withContext(ioDispatcher) {
87-
try {
88-
if (rawData.isEmpty()) {
89-
Log.e(TAG, "Cannot store credentials: rawData is empty")
90-
false
91-
} else {
92-
val credentialsStored = applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom(rawData)
93-
credentialsStored
145+
private suspend fun emitErrorFetching(urlLogin: UriLogin) = _onFinishedEvent.emit(
146+
NavigationActionData(
147+
showSiteSelector = false,
148+
showPostSignupInterstitial = false,
149+
siteUrl = urlLogin.siteUrl,
150+
oldSitesIDs = oldSitesIDs,
151+
isError = true
152+
)
153+
)
154+
155+
@SuppressWarnings("unused")
156+
@Subscribe(threadMode = ThreadMode.BACKGROUND)
157+
fun onSiteChanged(event: OnSiteChanged) {
158+
val currentNormalizedUrl = UrlUtils.normalizeUrl(currentUrlLogin?.siteUrl)
159+
val site = siteStore.sites.firstOrNull { UrlUtils.normalizeUrl(it.url) == currentNormalizedUrl }
160+
if (site == null) {
161+
appLogWrapper.e(AppLog.T.MAIN, "Site not found for URL: ${currentUrlLogin?.siteUrl}")
162+
viewModelScope.launch {
163+
_onFinishedEvent.emit(
164+
NavigationActionData(
165+
showSiteSelector = false,
166+
showPostSignupInterstitial = false,
167+
siteUrl = currentUrlLogin?.siteUrl,
168+
oldSitesIDs = oldSitesIDs,
169+
isError = true
170+
)
171+
)
94172
}
95-
} catch (e: Exception) {
96-
Log.e(TAG, "Error storing credentials", e)
97-
false
173+
} else {
174+
dispatcher.dispatch(SiteActionBuilder.newFetchProfileXmlRpcAction(site))
98175
}
99176
}
177+
178+
@SuppressWarnings("unused")
179+
@Subscribe(threadMode = ThreadMode.BACKGROUND)
180+
fun onProfileFetched(event: OnProfileFetched) {
181+
viewModelScope.launch {
182+
_onFinishedEvent.emit(
183+
NavigationActionData(
184+
showSiteSelector = siteStore.hasSite(),
185+
showPostSignupInterstitial = !siteStore.hasSite()
186+
&& appPrefsWrapper.shouldShowPostSignupInterstitial,
187+
siteUrl = event.site.url,
188+
oldSitesIDs = oldSitesIDs,
189+
isError = false
190+
)
191+
)
192+
}
193+
}
194+
195+
data class NavigationActionData(
196+
val showSiteSelector: Boolean,
197+
val showPostSignupInterstitial: Boolean,
198+
val siteUrl: String?,
199+
val oldSitesIDs: ArrayList<Int>?,
200+
val isError: Boolean
201+
)
100202
}

WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.wordpress.android.fluxc.utils.AppLogWrapper
1111
import org.wordpress.android.modules.BG_THREAD
1212
import org.wordpress.android.util.AppLog
1313
import org.wordpress.android.util.BuildConfigWrapper
14+
import org.wordpress.android.util.UrlUtils
1415
import rs.wordpress.api.kotlin.ApiDiscoveryResult
1516
import rs.wordpress.api.kotlin.WpLoginClient
1617
import javax.inject.Inject
@@ -77,7 +78,8 @@ class ApplicationPasswordLoginHelper @Inject constructor(
7778
if (uriLogin.user.isNullOrEmpty() || uriLogin.password.isNullOrEmpty() ) {
7879
false
7980
} else {
80-
val site = siteSqlUtils.getSites().firstOrNull { it.url == uriLogin.siteUrl }
81+
val normalizedUrl = UrlUtils.normalizeUrl(uriLogin.siteUrl)
82+
val site = siteSqlUtils.getSites().firstOrNull { UrlUtils.normalizeUrl(it.url) == normalizedUrl}
8183
if (site != null) {
8284
site.apply {
8385
apiRestUsernamePlain = uriLogin.user

0 commit comments

Comments
 (0)