Skip to content

Delete subscriber #21989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
11804b2
Created shell for adding subscribers
nbradbury Jul 2, 2025
49a1656
Fleshed out shell for adding subscribers
nbradbury Jul 2, 2025
91118dc
Fleshed out shell for adding subscribers (again)
nbradbury Jul 2, 2025
a7db1f4
Fixed broken text field
nbradbury Jul 2, 2025
b1f58b3
Changed result type of addSubscribers fun
nbradbury Jul 2, 2025
3bff7bc
Added progress
nbradbury Jul 2, 2025
318f1d9
Simplified layout
nbradbury Jul 3, 2025
c09b977
Fixed positioning in layout
nbradbury Jul 3, 2025
d18c101
Don't use onError when adding subscribers
nbradbury Jul 3, 2025
92d5e0b
Separate screens in SubscribersActivity
nbradbury Jul 3, 2025
e4b0068
Moved adding subscribers to its own view model
nbradbury Jul 3, 2025
6abbd66
Update WordPress/src/main/res/values/strings.xml
nbradbury Jul 3, 2025
50511d5
Update WordPress/src/main/java/org/wordpress/android/ui/subscribers/A…
nbradbury Jul 3, 2025
56da02e
Update WordPress/src/main/java/org/wordpress/android/ui/subscribers/A…
nbradbury Jul 3, 2025
d8fb9b4
Let view model handle success/failure
nbradbury Jul 3, 2025
b9f58f7
First pass at deleting subscriber
nbradbury Jul 3, 2025
84a4278
Added confirmation dialog for deletion
nbradbury Jul 3, 2025
8be7a50
Refresh list after successful deletion
nbradbury Jul 3, 2025
02ac3ac
Update WordPress/src/main/java/org/wordpress/android/ui/subscribers/A…
nbradbury Jul 3, 2025
f109165
Merge branch 'trunk' into feature/delete-subscriber
nbradbury Jul 3, 2025
9e3d04e
Use rememberSavable for the button state
nbradbury Jul 3, 2025
10c5b75
Update WordPress/src/main/java/org/wordpress/android/ui/subscribers/S…
nbradbury Jul 3, 2025
fbd7292
Use ioDispatcher for all network requests
nbradbury Jul 3, 2025
3bac5d9
First pass at using event observer for deleting subscriber
nbradbury Jul 3, 2025
6af224a
Clear uiEvent after handling
nbradbury Jul 3, 2025
188e700
Merge branch 'trunk' into feature/delete-subscriber
nbradbury Jul 6, 2025
dd455ab
Added a couple of comments
nbradbury Jul 7, 2025
257d866
Use SingleLiveEvent to simplify event observation and clearing
nbradbury Jul 7, 2025
5d4b534
Merge branch 'trunk' into feature/delete-subscriber
nbradbury Jul 7, 2025
a9c7f4e
Updated WP-rs version
nbradbury Jul 7, 2025
dc4a0a3
Merge remote-tracking branch 'origin/feature/delete-subscriber' into …
nbradbury Jul 7, 2025
cb92499
Reverted SingleLiveEvent due to state loss during configuration change
nbradbury Jul 7, 2025
b0241d4
Clear the event after setting it
nbradbury Jul 7, 2025
5edf421
Moved clearing the event to its own fun (again)
nbradbury Jul 7, 2025
c79619b
Use runCatching when deleting
nbradbury Jul 7, 2025
ddb5853
Use runCatching when adding a subscriber
nbradbury Jul 7, 2025
26282b1
Updated wordpress-rs hash
nbradbury Jul 7, 2025
f2330aa
Added a delay to deletion
nbradbury Jul 7, 2025
3013bf1
Remove local subscriber on successful deletion
nbradbury Jul 8, 2025
ad9a52f
Updated wordpress-rs version
nbradbury Jul 8, 2025
dd83cb5
Merge branch 'trunk' into feature/delete-subscriber
nbradbury Jul 8, 2025
e99baa0
Show deletion success dialog
nbradbury Jul 8, 2025
dda07ee
Merge branch 'trunk' into feature/delete-subscriber
nbradbury Jul 9, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class AddSubscribersViewModel @Inject constructor(
) {
launch(bgDispatcher) {
val result = addSubscribers(emails)
launch(mainDispatcher) {
withContext(mainDispatcher) {
if (result.isSuccess) {
toastUtilsWrapper.showToast(R.string.subscribers_add_success)
onSuccess()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fun SubscriberDetailScreen(
onUrlClick: (String) -> Unit,
onEmailClick: (String) -> Unit,
onPlanClick: (index: Int) -> Unit,
onDeleteClick: (subscriber: Subscriber) -> Unit,
modifier: Modifier = Modifier,
subscriberStats: State<IndividualSubscriberStats?>? = null
) {
Expand Down Expand Up @@ -92,7 +93,11 @@ fun SubscriberDetailScreen(

Spacer(modifier = Modifier.height(32.dp))

DeleteSubscriberButton()
DeleteSubscriberButton(
onClick = {
onDeleteClick(subscriber)
}
)
}
}

Expand Down Expand Up @@ -346,9 +351,13 @@ private fun DetailRow(
}

@Composable
private fun DeleteSubscriberButton() {
private fun DeleteSubscriberButton(
onClick: () -> Unit,
) {
Button(
onClick = { /* Handle delete action */ },
onClick = {
onClick()
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
Expand Down Expand Up @@ -399,7 +408,8 @@ fun SubscriberDetailScreenPreview() {
subscriberStats = remember { mutableStateOf(subscriberStats) },
onUrlClick = {},
onEmailClick = {},
onPlanClick = {}
onPlanClick = {},
onDeleteClick = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.wordpress.android.ui.subscribers

import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
Expand All @@ -17,6 +18,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
Expand All @@ -25,6 +27,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.wordpress.android.R
import org.wordpress.android.ui.ActivityLauncher
Expand All @@ -40,6 +43,7 @@ class SubscribersActivity : BaseAppCompatActivity() {
private val addSubscribersViewModel by viewModels<AddSubscribersViewModel>()

private lateinit var composeView: ComposeView
private lateinit var navController: NavHostController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -55,6 +59,18 @@ class SubscribersActivity : BaseAppCompatActivity() {
}
}
)

viewModel.uiEvent.observe(this) { event ->

This comment was marked as resolved.

when (event) {
is SubscribersViewModel.UiEvent.ShowDeleteConfirmationDialog -> {
showDeleteConfirmationDialog(event.subscriber, navController)
}

is SubscribersViewModel.UiEvent.ShowToast -> {
Toast.makeText(this, event.messageRes, Toast.LENGTH_SHORT).show()
}
}
}
}

private enum class SubscriberScreen {
Expand All @@ -67,10 +83,10 @@ class SubscribersActivity : BaseAppCompatActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NavigableContent() {
val navController = rememberNavController()
navController = rememberNavController()
val listTitle = stringResource(R.string.subscribers)
val titleState = remember { mutableStateOf(listTitle) }
val showAddSubscribersButtonState = remember { mutableStateOf(true) }
val showAddSubscribersButtonState = rememberSaveable { mutableStateOf(true) }

AppThemeM3 {
Scaffold(
Expand Down Expand Up @@ -235,6 +251,11 @@ class SubscribersActivity : BaseAppCompatActivity() {
)
navController.navigate(route = SubscriberScreen.Plan.name)
},
onDeleteClick = { _ ->
viewModel.onDeleteSubscriberClick(
subscriber = subscriber,
)
},
modifier = modifier,
subscriberStats = viewModel.subscriberStats.collectAsState()
)
Expand All @@ -260,6 +281,26 @@ class SubscribersActivity : BaseAppCompatActivity() {
)
}

private fun showDeleteConfirmationDialog(
subscriber: Subscriber,
navController: NavHostController
) {
MaterialAlertDialogBuilder(this).also { builder ->
builder.setTitle(R.string.subscribers_delete_confirmation_title)
builder.setMessage(R.string.subscribers_delete_confirmation_message)
builder.setPositiveButton(R.string.delete) { _, _ ->
viewModel.deleteSubscriberConfirmed(
subscriber = subscriber,
onSuccess = {
navController.navigateUp()
}
)
}
builder.setNegativeButton(R.string.cancel, null)
builder.show()
}
}

private fun onEmailClick(email: String) {
ActivityLauncher.openUrlExternal(this, "mailto:$email")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import javax.inject.Named

@HiltViewModel
class SubscribersViewModel @Inject constructor(
@Named(UI_THREAD) mainDispatcher: CoroutineDispatcher,
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
private val appLogWrapper: AppLogWrapper,
) : DataViewViewModel(
mainDispatcher = mainDispatcher,
Expand All @@ -44,6 +44,13 @@ class SubscribersViewModel @Inject constructor(
@Inject
lateinit var dateFormatWrapper: SimpleDateFormatWrapper

sealed class UiEvent {
data class ShowDeleteConfirmationDialog(val subscriber: Subscriber) : UiEvent()
data class ShowToast(val messageRes: Int) : UiEvent()
}
private val _uiEvent = MutableStateFlow<UiEvent?>(null)

This comment was marked as resolved.

val uiEvent = _uiEvent.asStateFlow()

override fun getSupportedFilters(): List<DataViewDropdownItem> {
return listOf(
DataViewDropdownItem(
Expand Down Expand Up @@ -215,6 +222,37 @@ class SubscribersViewModel @Inject constructor(
}
}

private suspend fun deleteSubscriber(subscriber: Subscriber): Result<Boolean> = withContext(ioDispatcher) {
val response = if (subscriber.isEmailSubscriber) {
wpComApiClient.request { requestBuilder ->
requestBuilder.followers().deleteEmailFollower(
wpComSiteId = siteId().toULong(),
subscriptionId = subscriber.subscriptionId
)
}
} else {
wpComApiClient.request { requestBuilder ->
requestBuilder.followers().deleteFollower(
wpComSiteId = siteId().toULong(),
userId = subscriber.userId
)
}
}
when (response) {
is WpRequestResult.Success -> {
appLogWrapper.d(AppLog.T.MAIN, "Delete subscriber success")
return@withContext Result.success(true)
}

else -> {
val error = (response as? WpRequestResult.WpError)?.errorMessage
appLogWrapper.e(AppLog.T.MAIN, "Delete subscriber failed: $error")
return@withContext Result.failure(Exception(error))
}
}
}


/**
* Called when an item in the list is clicked. We use this to request stats for the clicked subscriber.
*/
Expand All @@ -230,6 +268,36 @@ class SubscribersViewModel @Inject constructor(
}
}

/**
* Trigger the delete confirmation dialog when the user taps the delete button for a subscriber
*/
fun onDeleteSubscriberClick(subscriber: Subscriber) {
appLogWrapper.d(AppLog.T.MAIN, "Clicked on delete subscriber ${subscriber.displayNameOrEmail()}")
_uiEvent.value = UiEvent.ShowDeleteConfirmationDialog(subscriber)
}

/**
* Subscriber deletion has been confirmed by the user. Delete the subscriber and refresh the list.
*/
fun deleteSubscriberConfirmed(subscriber: Subscriber, onSuccess: () -> Unit) {
launch(ioDispatcher) {
val result = deleteSubscriber(subscriber = subscriber)
withContext(mainDispatcher) {
if (result.isSuccess) {
_uiEvent.value = UiEvent.ShowToast(R.string.subscribers_delete_success)
onSuccess()
onRefreshData()
} else {
_uiEvent.value = UiEvent.ShowToast(R.string.subscribers_delete_failed)
}
}
}
}

fun clearUiEvent() {
_uiEvent.value = null
}

companion object {
private const val ID_FILTER_EMAIL = 1L
private const val ID_FILTER_READER = 2L
Expand Down
4 changes: 4 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5031,6 +5031,10 @@ translators: %s: Select control option value e.g: "Auto, 25%". -->
</string>
<string name="subscribers_add_success">Successfully added subscribers</string>
<string name="subscribers_add_failed">Failed to add subscribers</string>
<string name="subscribers_delete_confirmation_title">Delete subscriber</string>
<string name="subscribers_delete_confirmation_message">Are you sure you want to delete this subscriber?</string>
<string name="subscribers_delete_success">Subscriber deleted</string>
<string name="subscribers_delete_failed">Failed to delete subscriber</string>

<!-- Application Password authentication -->
<string name="application_password_credentials_stored">Application password credentials stored for %1$s</string>
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ wiremock = '2.26.3'
wordpress-aztec = 'v2.1.4'
wordpress-lint = '2.1.0'
wordpress-persistent-edittext = '1.0.2'
wordpress-rs = 'trunk-14fe1179d108dc91066776e28ee9690864cab1f1'
wordpress-rs = 'trunk-342fd04e1f398fdf9167ae9f8a7618da10f10688'
wordpress-utils = '3.14.0'
automattic-ucrop = '2.2.11'
zendesk = '5.5.0'
Expand Down