From 85f0b1f320ca813707627aa65b94f50c42a9bf6b Mon Sep 17 00:00:00 2001 From: Martin Marconcini Date: Thu, 17 Aug 2023 22:26:46 +0200 Subject: [PATCH] Prompt the user before deleting a filter Prevent users from accidentally deleting filters by prompting them to confirm. Add an AlertDialog extension that converts AlertDialog callbacks to linear control flow. Fixes #3736. --- .../components/filters/EditFilterActivity.kt | 7 ++- .../components/filters/FilterExtensions.kt | 29 +++++++++ .../components/filters/FiltersActivity.kt | 7 ++- .../tusky/util/AlertDialogExtensions.kt | 63 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/filters/FilterExtensions.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/AlertDialogExtensions.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt index 2ef7902cc..f8a291c4b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt @@ -1,6 +1,7 @@ package com.keylesspalace.tusky.components.filters import android.content.Context +import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Bundle import android.view.View import android.widget.AdapterView @@ -81,7 +82,11 @@ class EditFilterActivity : BaseActivity() { binding.actionChip.setOnClickListener { showAddKeywordDialog() } binding.filterSaveButton.setOnClickListener { saveChanges() } - binding.filterDeleteButton.setOnClickListener { deleteFilter() } + binding.filterDeleteButton.setOnClickListener { + lifecycleScope.launch { + if (showDeleteFilterDialog(filter.title) == BUTTON_POSITIVE) deleteFilter() + } + } binding.filterDeleteButton.visible(originalFilter != null) for (switch in contextSwitches.keys) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/FilterExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/FilterExtensions.kt new file mode 100644 index 000000000..2a6c69d21 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/FilterExtensions.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . + */ + +package com.keylesspalace.tusky.components.filters + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.util.await + +internal suspend fun Activity.showDeleteFilterDialog(filterTitle: String) = AlertDialog.Builder(this) + .setMessage(getString(R.string.dialog_delete_filter_text, filterTitle)) + .setCancelable(true) + .create() + .await(R.string.dialog_delete_filter_positive_action, android.R.string.cancel) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt index 91d6b28d7..d66fb7ad2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt @@ -1,5 +1,6 @@ package com.keylesspalace.tusky.components.filters +import android.content.DialogInterface.BUTTON_POSITIVE import android.content.Intent import android.os.Bundle import androidx.activity.viewModels @@ -104,7 +105,11 @@ class FiltersActivity : BaseActivity(), FiltersListener { } override fun deleteFilter(filter: Filter) { - viewModel.deleteFilter(filter, binding.root) + lifecycleScope.launch { + if (showDeleteFilterDialog(filter.title) == BUTTON_POSITIVE) { + viewModel.deleteFilter(filter, binding.root) + } + } } override fun updateFilter(updatedFilter: Filter) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/AlertDialogExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/AlertDialogExtensions.kt new file mode 100644 index 000000000..b79671a53 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/AlertDialogExtensions.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . + */ + +package com.keylesspalace.tusky.util + +import android.content.DialogInterface +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Wait for the alert dialog buttons to be clicked, return the ID of the clicked button + * + * @param positiveText Text to show on the positive button + * @param negativeText Optional text to show on the negative button + * @param neutralText Optional text to show on the neutral button + */ +@OptIn(ExperimentalCoroutinesApi::class) +suspend fun AlertDialog.await( + positiveText: String, + negativeText: String? = null, + neutralText: String? = null +) = suspendCancellableCoroutine { cont -> + val listener = DialogInterface.OnClickListener { _, which -> + cont.resume(which) { dismiss() } + } + + setButton(AlertDialog.BUTTON_POSITIVE, positiveText, listener) + negativeText?.let { setButton(AlertDialog.BUTTON_NEGATIVE, it, listener) } + neutralText?.let { setButton(AlertDialog.BUTTON_NEUTRAL, it, listener) } + + setOnCancelListener { cont.cancel() } + cont.invokeOnCancellation { dismiss() } + show() +} + +/** + * @see [AlertDialog.await] + */ +suspend fun AlertDialog.await( + @StringRes positiveTextResource: Int, + @StringRes negativeTextResource: Int? = null, + @StringRes neutralTextResource: Int? = null +) = await( + context.getString(positiveTextResource), + negativeTextResource?.let { context.getString(it) }, + neutralTextResource?.let { context.getString(it) } +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06d25d4e7..fdf776200 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -818,4 +818,6 @@ Copy version and device information Copied version and device information Playback failed: %s + Delete filter \'%1$s\'?" + Delete