Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
9c1b3fb
Add close account button
mkevins May 8, 2023
db7bacb
Merge pull request #18382 from wordpress-mobile/feature/enable-accoun…
ovitrif May 8, 2023
9dd2b37
Add ui state and methods for account closure dialog
mkevins May 8, 2023
7edd583
Add account closure dialog
mkevins May 8, 2023
8fc1bb9
Extract flat button components
mkevins May 9, 2023
ea6edb6
Make some minor style adjustments to the dialog
mkevins May 9, 2023
6f824ad
Use extracted button components in dialog
mkevins May 9, 2023
b3971f7
Add ui state for atomic sites to the model
mkevins May 9, 2023
01b7724
Add ineligible dialog
mkevins May 9, 2023
e78ca07
Add ineligible dialog to the ui
mkevins May 9, 2023
a75a374
Add account closure origin for help requests
mkevins May 9, 2023
114db4e
Wire up contact support button to launch HelpActivity
mkevins May 9, 2023
7e481dd
Merge branch 'feature/enable-account-closure' into feature/enable-acc…
mkevins May 9, 2023
8900ab3
Add pending state to button
mkevins May 9, 2023
0fc22ea
Add pending flag to dialog state
mkevins May 9, 2023
99c8a6c
Add fake account closure implementation
mkevins May 9, 2023
1eca8a0
Wire up the confirm button to the close account function
mkevins May 9, 2023
48dcb30
Remove unused imports
ovitrif May 9, 2023
bdc3897
Merge pull request #18386 from wordpress-mobile/feature/enable-accoun…
ovitrif May 9, 2023
0563436
Remove unnecessary type-guard in fake closeAccount function
mkevins May 9, 2023
f478167
Refactor: remove unused imports
ovitrif May 9, 2023
8bb341f
Update: add inline magic number detekt suppression temporarily
ovitrif May 9, 2023
5060a01
Merge pull request #18391 from wordpress-mobile/feature/enable-accoun…
mkevins May 9, 2023
0af907f
Merge branch 'trunk' into feature/enable-account-closure
mkevins May 10, 2023
73f9e04
Update FluxC version to reference PR artifact
mkevins May 11, 2023
cf8f5ed
Extract a common dialog component for normal and error flows
mkevins May 10, 2023
dd105e2
Disable cancel button while account closure is pending
mkevins May 11, 2023
780799d
Add account closure error message strings
mkevins May 11, 2023
f84e272
Add dialog error ui
mkevins May 11, 2023
8bc21db
Add error handling to the view model
mkevins May 11, 2023
f4d8ec0
Add string for successful account closure
mkevins May 11, 2023
74ca2b5
Add dialog success ui
mkevins May 11, 2023
a99b301
Log user out after successful account closure
mkevins May 11, 2023
c96d531
Fix the names of composable preview functions
mkevins May 11, 2023
8118fa4
Make cancel button disabled without spinner while pending
mkevins May 11, 2023
d1323ef
Refactor user events to make logout smoother
mkevins May 11, 2023
939b22c
Autofocus username input when account closure dialog opens
mkevins May 11, 2023
b6bcdd9
Add account closure analytics events
mkevins May 16, 2023
58af67d
Add account closure tracking functions
mkevins May 16, 2023
d8dea83
Track success and failure of account closure
mkevins May 16, 2023
15cd55c
Re-enable pre-flight check for atomic sites
mkevins May 17, 2023
7cd6f4f
Merge branch 'trunk' into feature/enable-account-closure
mkevins May 17, 2023
63cceae
Merge branch 'feature/enable-account-closure' into feature/enable-acc…
mkevins May 17, 2023
7c5e25b
Merge branch 'feature/enable-account-closure--18393-add-api-endpoint'…
mkevins May 17, 2023
0cc62cb
Merge branch 'feature/enable-account-closure--18406-add-error-handlin…
mkevins May 17, 2023
4d0eba1
Update FluxC reference
mkevins May 17, 2023
8e713c4
Merge branch 'feature/enable-account-closure--18393-add-api-endpoint'…
mkevins May 17, 2023
a6cd729
Merge branch 'feature/enable-account-closure--18406-add-error-handlin…
mkevins May 17, 2023
0d0b431
Merge pull request #18407 from wordpress-mobile/feature/enable-accoun…
mkevins May 17, 2023
65b2522
Merge pull request #18444 from wordpress-mobile/feature/enable-accoun…
ovitrif May 17, 2023
f3fab89
Fix compilation issues in account settings vm
ovitrif May 17, 2023
934d626
Merge pull request #18450 from wordpress-mobile/feature/enable-accoun…
mkevins May 17, 2023
488dc3f
Add test for getSitesUseCase
mkevins May 17, 2023
19d7cc6
Fix typo in test name
mkevins May 17, 2023
93f9741
Extract account closure function to a use case class
mkevins May 18, 2023
6c216d5
Add unit tests for account closure ui state
mkevins May 18, 2023
011c091
Merge pull request #18409 from wordpress-mobile/feature/enable-accoun…
ovitrif May 18, 2023
bdf5833
Removes empty line to fix lint issue
antonis May 18, 2023
229c44c
Merge pull request #18457 from wordpress-mobile/feature/enable-accoun…
antonis May 18, 2023
92f1812
Merge branch 'trunk' into feature/enable-account-closure
mkevins May 19, 2023
6e49556
Add release note
mkevins May 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

22.5
-----

* [*] Adds a button to enable account closure from the account settings screen [https://github.com/wordpress-mobile/WordPress-Android/pull/18412]

22.4
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ class HelpActivity : LocaleAwareActivity() {
JETPACK_MIGRATION_HELP("origin:jetpack-migration-help"),
JETPACK_INSTALL_FULL_PLUGIN_ONBOARDING("origin:jp-install-full-plugin-overlay"),
JETPACK_INSTALL_FULL_PLUGIN_ERROR("origin:jp-install-full-plugin-error"),
JETPACK_REMOTE_INSTALL_PLUGIN_ERROR("origin:jp-remote-install-plugin-error");
JETPACK_REMOTE_INSTALL_PLUGIN_ERROR("origin:jp-remote-install-plugin-error"),
ACCOUNT_CLOSURE_DIALOG("origin:account-closure-dialog");

override fun toString(): String {
return stringValue
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.wordpress.android.ui.prefs.accountsettings

import org.wordpress.android.analytics.AnalyticsTracker.Stat
import org.wordpress.android.analytics.AnalyticsTracker.Stat.CLOSED_ACCOUNT
import org.wordpress.android.analytics.AnalyticsTracker.Stat.CLOSE_ACCOUNT_FAILED
import org.wordpress.android.analytics.AnalyticsTracker.Stat.SETTINGS_DID_CHANGE
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.EMAIL_CHANGED
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.PASSWORD_CHANGED
Expand All @@ -17,6 +19,7 @@ private const val SOURCE_ACCOUNT_SETTINGS = "account_settings"
private const val TRACK_PROPERTY_FIELD_NAME = "field_name"
private const val TRACK_PROPERTY_PAGE = "page"
private const val TRACK_PROPERTY_PAGE_ACCOUNT_SETTINGS = "account_settings"
private const val KEY_ACCOUNT_CLOSURE_ERROR_CODE = "error_code"

enum class AccountSettingsEvent(val trackProperty: String? = null) {
EMAIL_CHANGED("email"),
Expand Down Expand Up @@ -50,4 +53,16 @@ class AccountSettingsAnalyticsTracker @Inject constructor(private val analyticsT
props[SOURCE] = SOURCE_ACCOUNT_SETTINGS
analyticsTracker.track(stat, props)
}

fun trackAccountClosureFailure(errorCode: String?) {
mutableMapOf<String, String?>().apply {
put(KEY_ACCOUNT_CLOSURE_ERROR_CODE, errorCode ?: "unknown")
}.let { props ->
analyticsTracker.track(CLOSE_ACCOUNT_FAILED, props)
}
}

fun trackAccountClosureSuccess() {
analyticsTracker.track(CLOSED_ACCOUNT)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import android.widget.TextView
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import org.wordpress.android.R
import org.wordpress.android.WordPress
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.accounts.HelpActivity
import org.wordpress.android.ui.accounts.signup.BaseUsernameChangerFullScreenDialogFragment
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.ui.pages.SnackbarMessageHolder
import org.wordpress.android.ui.prefs.DetailListPreference
import org.wordpress.android.ui.prefs.EditTextPreferenceWithValidation
Expand All @@ -37,10 +45,13 @@ import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.USERN
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.USERNAME_CHANGE_SCREEN_DISPLAYED
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsEvent.WEB_ADDRESS_CHANGED
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountSettingsUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.ChangePasswordSettingsUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.EmailSettingsUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.PrimarySiteSettingsUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.UserNameSettingsUiState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.Companion.AccountClosureAction
import org.wordpress.android.ui.prefs.accountsettings.components.AccountClosureUi
import org.wordpress.android.ui.utils.UiHelpers
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T.SETTINGS
Expand Down Expand Up @@ -90,6 +101,7 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
addPreferencesFromResource(R.xml.account_settings)
bindPreferences()
setUpListeners()
observeAccountClosureEvents()
emailPreference.configure(
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
validationType = EMAIL
Expand Down Expand Up @@ -117,6 +129,30 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
changePasswordPreference.summary = EMPTY_STRING
}

private fun observeAccountClosureEvents() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userActionEvents.collect { handleUserAction(it) }
}
}
lifecycleScope.launch {
// Using `CREATED` state here prevents tracking duplicate events
repeatOnLifecycle(Lifecycle.State.CREATED) {
viewModel.accountClosureUiState.collect {
when (it) {
is AccountClosureUiState.Opened.Error -> {
analyticsTracker.trackAccountClosureFailure(it.errorType.token)
}
is AccountClosureUiState.Opened.Success -> {
analyticsTracker.trackAccountClosureSuccess()
}
else -> {}
}
}
}
}
}

private fun setUpListeners() {
usernamePreference.onPreferenceClickListener = this@AccountSettingsFragment
primarySitePreference.onPreferenceChangeListener = this@AccountSettingsFragment
Expand Down Expand Up @@ -153,6 +189,21 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
return coordinatorView
}

@Deprecated("Deprecated")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(view.findViewById<View>(android.R.id.list) as? ListView)?.let { listView ->
listView.addFooterView(ComposeView(context).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
AppTheme {
AccountClosureUi(viewModel)
}
}
})
}
}

@Deprecated("Deprecated")
override fun onStart() {
super.onStart()
Expand Down Expand Up @@ -349,4 +400,26 @@ class AccountSettingsFragment : PreferenceFragmentLifeCycleOwner(),
}
}
}

private fun handleUserAction(action: AccountClosureAction) {
when (action) {
AccountClosureAction.HELP_VIEWED -> viewHelp()
AccountClosureAction.ACCOUNT_CLOSED -> signOut()
AccountClosureAction.USER_LOGGED_OUT -> {
ActivityLauncher.showMainActivity(context, true)
}
}
}
private fun viewHelp() = ActivityLauncher.viewHelp(
context,
HelpActivity.Origin.ACCOUNT_CLOSURE_DIALOG,
null,
null,
)

private fun signOut() {
(activity.application as? WordPress)?.let {
viewModel.signOutWordPress(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,30 @@ import androidx.lifecycle.viewModelScope
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.wordpress.android.R
import org.wordpress.android.WordPress
import org.wordpress.android.fluxc.network.rest.wpcom.account.CloseAccountResult
import org.wordpress.android.fluxc.store.AccountStore.AccountError
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_FETCH_GENERIC_ERROR
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_FETCH_REAUTHORIZATION_REQUIRED_ERROR
import org.wordpress.android.fluxc.store.AccountStore.AccountErrorType.SETTINGS_POST_ERROR
import org.wordpress.android.fluxc.store.AccountStore.OnAccountChanged
import org.wordpress.android.modules.BG_THREAD
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.ui.pages.SnackbarMessageHolder
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Dismissed
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Error
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Default
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened.Success
import org.wordpress.android.ui.prefs.accountsettings.usecase.AccountClosureUseCase
import org.wordpress.android.ui.prefs.accountsettings.usecase.FetchAccountSettingsUseCase
import org.wordpress.android.ui.prefs.accountsettings.usecase.GetAccountUseCase
import org.wordpress.android.ui.prefs.accountsettings.usecase.GetSitesUseCase
Expand All @@ -38,15 +49,21 @@ class AccountSettingsViewModel @Inject constructor(
private val resourceProvider: ResourceProvider,
networkUtilsWrapper: NetworkUtilsWrapper,
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
private val fetchAccountSettingsUseCase: FetchAccountSettingsUseCase,
private val pushAccountSettingsUseCase: PushAccountSettingsUseCase,
private val getAccountUseCase: GetAccountUseCase,
private val getSitesUseCase: GetSitesUseCase,
private val optimisticUpdateHandler: AccountSettingsOptimisticUpdateHandler
private val optimisticUpdateHandler: AccountSettingsOptimisticUpdateHandler,
private val accountClosureUseCase: AccountClosureUseCase,
) : ScopedViewModel(mainDispatcher) {
private var fetchNewSettingsJob: Job? = null
private var _accountSettingsUiState = MutableStateFlow(getAccountSettingsUiState(true))
val accountSettingsUiState: StateFlow<AccountSettingsUiState> = _accountSettingsUiState.asStateFlow()
private var _accountClosureUiState = MutableStateFlow<AccountClosureUiState>(Dismissed)
val accountClosureUiState: StateFlow<AccountClosureUiState> = _accountClosureUiState
private var _userActionEvents = MutableSharedFlow<AccountClosureAction>()
val userActionEvents: SharedFlow<AccountClosureAction> = _userActionEvents

init {
viewModelScope.launch {
Expand Down Expand Up @@ -288,8 +305,73 @@ class AccountSettingsViewModel @Inject constructor(
val toastMessage: String?
)

sealed class AccountClosureUiState {
object Dismissed: AccountClosureUiState()

sealed class Opened: AccountClosureUiState() {
data class Default(val username: String?, val isPending: Boolean = false): Opened()
data class Error(val errorType: CloseAccountResult.ErrorType): Opened()
object Success: Opened()
}
}

fun openAccountClosureDialog() {
launch {
_accountClosureUiState.value = if (getSitesUseCase.getAtomic().isNotEmpty()) {
Error(CloseAccountResult.ErrorType.ATOMIC_SITE)
} else {
Default(username = getAccountUseCase.account.userName)
}
}
}
fun dismissAccountClosureDialog() {
_accountClosureUiState.value = Dismissed
}

fun closeAccount() {
(accountClosureUiState.value as? Default)?.let { uiState ->
_accountClosureUiState.value = uiState.copy(isPending = true)

launch {
accountClosureUseCase.closeAccount(
onResult = {
when(it) {
is CloseAccountResult.Success -> {
_accountClosureUiState.value = Success
}
is CloseAccountResult.Failure -> {
_accountClosureUiState.value = Error(it.error.errorType)
}
}
}
)
}
}
}

fun signOutWordPress(application: WordPress) {
launch {
withContext(bgDispatcher) {
application.wordPressComSignOut()
userAction(AccountClosureAction.USER_LOGGED_OUT)
}
}
}

fun userAction(action: AccountClosureAction) {
launch {
_userActionEvents.emit(action)
}
}

override fun onCleared() {
pushAccountSettingsUseCase.onCleared()
super.onCleared()
}

companion object {
enum class AccountClosureAction {
HELP_VIEWED, ACCOUNT_CLOSED, USER_LOGGED_OUT;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.wordpress.android.ui.prefs.accountsettings.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog

@Composable
fun AccountClosureDialog(
onDismissRequest: () -> Unit,
content: @Composable ColumnScope.() -> Unit,
) {
val padding = 10.dp
Dialog(onDismissRequest = onDismissRequest) {
Column(
modifier = Modifier
.clip(shape = RoundedCornerShape(padding))
.background(MaterialTheme.colors.background)
.padding(padding),
content = content,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.wordpress.android.ui.prefs.accountsettings.components

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.AccountClosureUiState.Opened
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.Companion.AccountClosureAction.ACCOUNT_CLOSED
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel.Companion.AccountClosureAction.HELP_VIEWED

@Composable
fun AccountClosureUi(viewModel: AccountSettingsViewModel) {
val uiState = viewModel.accountClosureUiState.collectAsState()

CloseAccountButton(onClick = { viewModel.openAccountClosureDialog() })

(uiState.value as? Opened)?.let {
AccountClosureDialog(
onDismissRequest = { viewModel.dismissAccountClosureDialog() },
) {
when(it) {
is Opened.Default -> it.username?.let { currentUsername ->
DialogUi(
currentUsername = currentUsername,
isPending = it.isPending,
onCancel = { viewModel.dismissAccountClosureDialog() },
onConfirm = { viewModel.closeAccount() },
)
}

is Opened.Error -> DialogErrorUi(
onDismissRequest = { viewModel.dismissAccountClosureDialog() },
onHelpRequested = { viewModel.userAction(HELP_VIEWED) },
it.errorType,
)
is Opened.Success -> DialogSuccessUi(
onDismissRequest = { viewModel.userAction(ACCOUNT_CLOSED) }
)
}
}
}
}
Loading