Skip to content

Commit 55d5762

Browse files
author
Thomas Horta
committed
Merge branch 'trunk' of github.com:wordpress-mobile/WordPress-Android into issue/18441-site-editor-remote-ff
2 parents 62b1df4 + 4d2eff9 commit 55d5762

File tree

23 files changed

+760
-10
lines changed

23 files changed

+760
-10
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
22.5
44
-----
55
* [***] Enables editing of the site homepage for sites using block-based themes directly from the pages list. [https://github.com/wordpress-mobile/WordPress-Android/pull/18454]
6+
* [*] Adds a button to enable account closure from the account settings screen [https://github.com/wordpress-mobile/WordPress-Android/pull/18412]
67

78
22.4
89
-----

WordPress/src/androidTest/java/org/wordpress/android/e2e/DashboardTests.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.wordpress.android.e2e
33
import dagger.hilt.android.testing.HiltAndroidTest
44
import org.junit.Assume.assumeTrue
55
import org.junit.Before
6+
import org.junit.Ignore
67
import org.junit.Test
78
import org.wordpress.android.e2e.pages.MySitesPage
89
import org.wordpress.android.support.BaseTest
@@ -21,6 +22,7 @@ class DashboardTests : BaseTest() {
2122
}
2223

2324
@Test
25+
@Ignore("Skipped due to increased flakiness. Test fails to scroll to the card.")
2426
fun e2eDomainsCardNavigation() {
2527
MySitesPage()
2628
.scrollToDomainsCard()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,8 @@ class HelpActivity : LocaleAwareActivity() {
363363
JETPACK_MIGRATION_HELP("origin:jetpack-migration-help"),
364364
JETPACK_INSTALL_FULL_PLUGIN_ONBOARDING("origin:jp-install-full-plugin-overlay"),
365365
JETPACK_INSTALL_FULL_PLUGIN_ERROR("origin:jp-install-full-plugin-error"),
366-
JETPACK_REMOTE_INSTALL_PLUGIN_ERROR("origin:jp-remote-install-plugin-error");
366+
JETPACK_REMOTE_INSTALL_PLUGIN_ERROR("origin:jp-remote-install-plugin-error"),
367+
ACCOUNT_CLOSURE_DIALOG("origin:account-closure-dialog");
367368

368369
override fun toString(): String {
369370
return stringValue

WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,13 +814,13 @@ class MySiteViewModel @Inject constructor(
814814
}
815815
add(Type.QUICK_LINK_RIBBON)
816816
add(Type.JETPACK_INSTALL_FULL_PLUGIN_CARD)
817+
add(Type.DOMAIN_REGISTRATION_CARD)
817818
}
818819

819820
MySiteTabType.DASHBOARD -> mutableListOf<Type>().apply {
820821
if (defaultTab == MySiteTabType.SITE_MENU) {
821822
add(Type.QUICK_START_CARD)
822823
}
823-
add(Type.DOMAIN_REGISTRATION_CARD)
824824
add(Type.QUICK_ACTIONS_CARD)
825825
}
826826

WordPress/src/main/java/org/wordpress/android/ui/prefs/accountsettings/AccountSettingsAnalyticsTracker.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.wordpress.android.ui.prefs.accountsettings
22

33
import org.wordpress.android.analytics.AnalyticsTracker.Stat
4+
import org.wordpress.android.analytics.AnalyticsTracker.Stat.CLOSED_ACCOUNT
5+
import org.wordpress.android.analytics.AnalyticsTracker.Stat.CLOSE_ACCOUNT_FAILED
46
import org.wordpress.android.analytics.AnalyticsTracker.Stat.SETTINGS_DID_CHANGE
57
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.EMAIL_CHANGED
68
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.PASSWORD_CHANGED
@@ -17,6 +19,7 @@ private const val SOURCE_ACCOUNT_SETTINGS = "account_settings"
1719
private const val TRACK_PROPERTY_FIELD_NAME = "field_name"
1820
private const val TRACK_PROPERTY_PAGE = "page"
1921
private const val TRACK_PROPERTY_PAGE_ACCOUNT_SETTINGS = "account_settings"
22+
private const val KEY_ACCOUNT_CLOSURE_ERROR_CODE = "error_code"
2023

2124
enum class AccountSettingsEvent(val trackProperty: String? = null) {
2225
EMAIL_CHANGED("email"),
@@ -50,4 +53,16 @@ class AccountSettingsAnalyticsTracker @Inject constructor(private val analyticsT
5053
props[SOURCE] = SOURCE_ACCOUNT_SETTINGS
5154
analyticsTracker.track(stat, props)
5255
}
56+
57+
fun trackAccountClosureFailure(errorCode: String?) {
58+
mutableMapOf<String, String?>().apply {
59+
put(KEY_ACCOUNT_CLOSURE_ERROR_CODE, errorCode ?: "unknown")
60+
}.let { props ->
61+
analyticsTracker.track(CLOSE_ACCOUNT_FAILED, props)
62+
}
63+
}
64+
65+
fun trackAccountClosureSuccess() {
66+
analyticsTracker.track(CLOSED_ACCOUNT)
67+
}
5368
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/accountsettings/AccountSettingsFragment.kt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@ import android.view.View
1414
import android.view.ViewGroup
1515
import android.widget.ListView
1616
import android.widget.TextView
17+
import androidx.compose.ui.platform.ComposeView
18+
import androidx.compose.ui.platform.ViewCompositionStrategy
1719
import androidx.coordinatorlayout.widget.CoordinatorLayout
1820
import androidx.core.view.ViewCompat
21+
import androidx.lifecycle.Lifecycle
22+
import androidx.lifecycle.repeatOnLifecycle
1923
import com.google.android.material.snackbar.BaseTransientBottomBar
2024
import com.google.android.material.snackbar.Snackbar
25+
import kotlinx.coroutines.launch
2126
import org.wordpress.android.R
2227
import org.wordpress.android.WordPress
28+
import org.wordpress.android.ui.ActivityLauncher
29+
import org.wordpress.android.ui.accounts.HelpActivity
2330
import org.wordpress.android.ui.accounts.signup.BaseUsernameChangerFullScreenDialogFragment
31+
import org.wordpress.android.ui.compose.theme.AppTheme
2432
import org.wordpress.android.ui.pages.SnackbarMessageHolder
2533
import org.wordpress.android.ui.prefs.DetailListPreference
2634
import org.wordpress.android.ui.prefs.EditTextPreferenceWithValidation
@@ -37,10 +45,13 @@ import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.USERN
3745
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.USERNAME_CHANGE_SCREEN_DISPLAYED
3846
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.WEB_ADDRESS_CHANGED
3947
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountSettingsUiState
48+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState
4049
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.ChangePasswordSettingsUiState
4150
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.EmailSettingsUiState
4251
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.PrimarySiteSettingsUiState
4352
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.UserNameSettingsUiState
53+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.Companion.AccountClosureAction
54+
import org.wordpress.android.ui.prefs.accountsettings.components.AccountClosureUi
4455
import org.wordpress.android.ui.utils.UiHelpers
4556
import org.wordpress.android.util.AppLog
4657
import org.wordpress.android.util.AppLog.T.SETTINGS
@@ -90,6 +101,7 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
90101
addPreferencesFromResource(R.xml.account_settings)
91102
bindPreferences()
92103
setUpListeners()
104+
observeAccountClosureEvents()
93105
emailPreference.configure(
94106
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
95107
validationType = EMAIL
@@ -117,6 +129,30 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
117129
changePasswordPreference.summary = EMPTY_STRING
118130
}
119131

132+
private fun observeAccountClosureEvents() {
133+
lifecycleScope.launch {
134+
repeatOnLifecycle(Lifecycle.State.STARTED) {
135+
viewModel.userActionEvents.collect { handleUserAction(it) }
136+
}
137+
}
138+
lifecycleScope.launch {
139+
// Using `CREATED` state here prevents tracking duplicate events
140+
repeatOnLifecycle(Lifecycle.State.CREATED) {
141+
viewModel.accountClosureUiState.collect {
142+
when (it) {
143+
is AccountClosureUiState.Opened.Error -> {
144+
analyticsTracker.trackAccountClosureFailure(it.errorType.token)
145+
}
146+
is AccountClosureUiState.Opened.Success -> {
147+
analyticsTracker.trackAccountClosureSuccess()
148+
}
149+
else -> {}
150+
}
151+
}
152+
}
153+
}
154+
}
155+
120156
private fun setUpListeners() {
121157
usernamePreference.onPreferenceClickListener = this@AccountSettingsFragment
122158
primarySitePreference.onPreferenceChangeListener = this@AccountSettingsFragment
@@ -153,6 +189,21 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
153189
return coordinatorView
154190
}
155191

192+
@Deprecated("Deprecated")
193+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
194+
super.onViewCreated(view, savedInstanceState)
195+
(view.findViewById<View>(android.R.id.list) as? ListView)?.let { listView ->
196+
listView.addFooterView(ComposeView(context).apply {
197+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
198+
setContent {
199+
AppTheme {
200+
AccountClosureUi(viewModel)
201+
}
202+
}
203+
})
204+
}
205+
}
206+
156207
@Deprecated("Deprecated")
157208
override fun onStart() {
158209
super.onStart()
@@ -349,4 +400,26 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
349400
}
350401
}
351402
}
403+
404+
private fun handleUserAction(action: AccountClosureAction) {
405+
when (action) {
406+
AccountClosureAction.HELP_VIEWED -> viewHelp()
407+
AccountClosureAction.ACCOUNT_CLOSED -> signOut()
408+
AccountClosureAction.USER_LOGGED_OUT -> {
409+
ActivityLauncher.showMainActivity(context, true)
410+
}
411+
}
412+
}
413+
private fun viewHelp() = ActivityLauncher.viewHelp(
414+
context,
415+
HelpActivity.Origin.ACCOUNT_CLOSURE_DIALOG,
416+
null,
417+
null,
418+
)
419+
420+
private fun signOut() {
421+
(activity.application as? WordPress)?.let {
422+
viewModel.signOutWordPress(it)
423+
}
424+
}
352425
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/accountsettings/AccountSettingsViewModel.kt

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,30 @@ import androidx.lifecycle.viewModelScope
55
import com.google.android.material.snackbar.Snackbar
66
import kotlinx.coroutines.CoroutineDispatcher
77
import kotlinx.coroutines.Job
8+
import kotlinx.coroutines.flow.MutableSharedFlow
89
import kotlinx.coroutines.flow.MutableStateFlow
10+
import kotlinx.coroutines.flow.SharedFlow
911
import kotlinx.coroutines.flow.StateFlow
1012
import kotlinx.coroutines.flow.asStateFlow
1113
import kotlinx.coroutines.flow.update
1214
import kotlinx.coroutines.launch
15+
import kotlinx.coroutines.withContext
1316
import org.wordpress.android.R
17+
import org.wordpress.android.WordPress
18+
import org.wordpress.android.fluxc.network.rest.wpcom.account.CloseAccountResult
1419
import org.wordpress.android.fluxc.store.AccountStore.AccountError
1520
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_FETCH_GENERIC_ERROR
1621
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_FETCH_REAUTHORIZATION_REQUIRED_ERROR
1722
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_POST_ERROR
1823
import org.wordpress.android.fluxc.store.AccountStore.OnAccountChanged
24+
import org.wordpress.android.modules.BG_THREAD
1925
import org.wordpress.android.modules.UI_THREAD
2026
import org.wordpress.android.ui.pages.SnackbarMessageHolder
27+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Dismissed
28+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Error
29+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Default
30+
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Success
31+
import org.wordpress.android.ui.prefs.accountsettings.usecase.AccountClosureUseCase
2132
import org.wordpress.android.ui.prefs.accountsettings.usecase.FetchAccountSettingsUseCase
2233
import org.wordpress.android.ui.prefs.accountsettings.usecase.GetAccountUseCase
2334
import org.wordpress.android.ui.prefs.accountsettings.usecase.GetSitesUseCase
@@ -38,15 +49,21 @@ class AccountSettingsViewModel @Inject constructor(
3849
private val resourceProvider: ResourceProvider,
3950
networkUtilsWrapper: NetworkUtilsWrapper,
4051
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
52+
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
4153
private val fetchAccountSettingsUseCase: FetchAccountSettingsUseCase,
4254
private val pushAccountSettingsUseCase: PushAccountSettingsUseCase,
4355
private val getAccountUseCase: GetAccountUseCase,
4456
private val getSitesUseCase: GetSitesUseCase,
45-
private val optimisticUpdateHandler: AccountSettingsOptimisticUpdateHandler
57+
private val optimisticUpdateHandler: AccountSettingsOptimisticUpdateHandler,
58+
private val accountClosureUseCase: AccountClosureUseCase,
4659
) : ScopedViewModel(mainDispatcher) {
4760
private var fetchNewSettingsJob: Job? = null
4861
private var _accountSettingsUiState = MutableStateFlow(getAccountSettingsUiState(true))
4962
val accountSettingsUiState: StateFlow<AccountSettingsUiState> = _accountSettingsUiState.asStateFlow()
63+
private var _accountClosureUiState = MutableStateFlow<AccountClosureUiState>(Dismissed)
64+
val accountClosureUiState: StateFlow<AccountClosureUiState> = _accountClosureUiState
65+
private var _userActionEvents = MutableSharedFlow<AccountClosureAction>()
66+
val userActionEvents: SharedFlow<AccountClosureAction> = _userActionEvents
5067

5168
init {
5269
viewModelScope.launch {
@@ -288,8 +305,72 @@ class AccountSettingsViewModel @Inject constructor(
288305
val toastMessage: String?
289306
)
290307

308+
sealed class AccountClosureUiState {
309+
object Dismissed: AccountClosureUiState()
310+
311+
sealed class Opened: AccountClosureUiState() {
312+
data class Default(val username: String?, val isPending: Boolean = false): Opened()
313+
data class Error(val errorType: CloseAccountResult.ErrorType): Opened()
314+
object Success: Opened()
315+
}
316+
}
317+
318+
fun openAccountClosureDialog() {
319+
launch {
320+
_accountClosureUiState.value = if (getSitesUseCase.getAtomic().isNotEmpty()) {
321+
Error(CloseAccountResult.ErrorType.ATOMIC_SITE)
322+
} else {
323+
Default(username = getAccountUseCase.account.userName)
324+
}
325+
}
326+
}
327+
fun dismissAccountClosureDialog() {
328+
_accountClosureUiState.value = Dismissed
329+
}
330+
331+
fun closeAccount() {
332+
(accountClosureUiState.value as? Default)?.let { uiState ->
333+
_accountClosureUiState.value = uiState.copy(isPending = true)
334+
335+
launch {
336+
accountClosureUseCase.closeAccount { result ->
337+
when (result) {
338+
is CloseAccountResult.Success -> {
339+
_accountClosureUiState.value = Success
340+
}
341+
342+
is CloseAccountResult.Failure -> {
343+
_accountClosureUiState.value = Error(result.error.errorType)
344+
}
345+
}
346+
}
347+
}
348+
}
349+
}
350+
351+
fun signOutWordPress(application: WordPress) {
352+
launch {
353+
withContext(bgDispatcher) {
354+
application.wordPressComSignOut()
355+
userAction(AccountClosureAction.USER_LOGGED_OUT)
356+
}
357+
}
358+
}
359+
360+
fun userAction(action: AccountClosureAction) {
361+
launch {
362+
_userActionEvents.emit(action)
363+
}
364+
}
365+
291366
override fun onCleared() {
292367
pushAccountSettingsUseCase.onCleared()
293368
super.onCleared()
294369
}
370+
371+
companion object {
372+
enum class AccountClosureAction {
373+
HELP_VIEWED, ACCOUNT_CLOSED, USER_LOGGED_OUT;
374+
}
375+
}
295376
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.wordpress.android.ui.prefs.accountsettings.components
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.ColumnScope
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.foundation.layout.widthIn
8+
import androidx.compose.foundation.shape.RoundedCornerShape
9+
import androidx.compose.material.MaterialTheme
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.ExperimentalComposeUiApi
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.clip
14+
import androidx.compose.ui.unit.dp
15+
import androidx.compose.ui.window.Dialog
16+
import androidx.compose.ui.window.DialogProperties
17+
18+
@OptIn(ExperimentalComposeUiApi::class)
19+
@Composable
20+
fun AccountClosureDialog(
21+
onDismissRequest: () -> Unit,
22+
content: @Composable ColumnScope.() -> Unit,
23+
) {
24+
val padding = 10.dp
25+
Dialog(
26+
onDismissRequest = onDismissRequest,
27+
properties = DialogProperties(
28+
usePlatformDefaultWidth = false,
29+
),
30+
) {
31+
Column(
32+
modifier = Modifier
33+
.widthIn(max = 320.dp)
34+
.clip(shape = RoundedCornerShape(padding))
35+
.background(MaterialTheme.colors.background)
36+
.padding(padding),
37+
content = content,
38+
)
39+
}
40+
}

0 commit comments

Comments
 (0)