From 485a4c364ed97240c0f5819522c8cde5444e5f87 Mon Sep 17 00:00:00 2001 From: Prat <616399+pt2121@users.noreply.github.com> Date: Tue, 13 Jun 2023 06:45:17 -0700 Subject: [PATCH] Improve prompts when draft is empty (#3699) When the user is closing the compose view, if it's new and empty, don't show a prompt. if it's an existing draft and now empty, ask if the user wants to delete it or continue editing. I don't think there is much value in saving an empty draft. --------- --- .../components/compose/ComposeActivity.kt | 41 ++++++++++++++----- .../components/compose/ComposeViewModel.kt | 40 ++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 2a2949a7..d5862dad 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -78,6 +78,7 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.EmojiAdapter import com.keylesspalace.tusky.adapter.LocaleAdapter import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener +import com.keylesspalace.tusky.components.compose.ComposeViewModel.ConfirmationKind import com.keylesspalace.tusky.components.compose.dialog.CaptionDialog import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog @@ -1126,16 +1127,19 @@ class ComposeActivity : private fun handleCloseButton() { val contentText = binding.composeEditField.text.toString() val contentWarning = binding.composeContentWarningField.text.toString() - if (viewModel.didChange(contentText, contentWarning)) { - when (viewModel.composeKind) { - ComposeKind.NEW -> getSaveAsDraftOrDiscardDialog(contentText, contentWarning) - ComposeKind.EDIT_DRAFT -> getUpdateDraftOrDiscardDialog(contentText, contentWarning) - ComposeKind.EDIT_POSTED -> getContinueEditingOrDiscardDialog() - ComposeKind.EDIT_SCHEDULED -> getContinueEditingOrDiscardDialog() - }.show() - } else { - viewModel.stopUploads() - finishWithoutSlideOutAnimation() + when (viewModel.handleCloseButton(contentText, contentWarning)) { + ConfirmationKind.NONE -> { + viewModel.stopUploads() + finishWithoutSlideOutAnimation() + } + ConfirmationKind.SAVE_OR_DISCARD -> + getSaveAsDraftOrDiscardDialog(contentText, contentWarning).show() + ConfirmationKind.UPDATE_OR_DISCARD -> + getUpdateDraftOrDiscardDialog(contentText, contentWarning).show() + ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_CHANGES -> + getContinueEditingOrDiscardDialog().show() + ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_DRAFT -> + getDeleteEmptyDraftOrContinueEditing().show() } } @@ -1200,6 +1204,23 @@ class ComposeActivity : } } + /** + * User is editing an existing draft and making it empty. + * The user can either delete the empty draft or go back to editing. + */ + private fun getDeleteEmptyDraftOrContinueEditing(): AlertDialog.Builder { + return AlertDialog.Builder(this) + .setMessage(R.string.compose_delete_draft) + .setPositiveButton(R.string.action_delete) { _, _ -> + viewModel.deleteDraft() + viewModel.stopUploads() + finishWithoutSlideOutAnimation() + } + .setNegativeButton(R.string.action_continue_edit) { _, _ -> + // Do nothing, dialog will dismiss, user can continue editing + } + } + private fun deleteDraftAndFinish() { viewModel.deleteDraft() finishWithoutSlideOutAnimation() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 38024f05..90389d28 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -21,6 +21,7 @@ import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.connyduck.calladapter.networkresult.fold +import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeKind import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult import com.keylesspalace.tusky.components.drafts.DraftHelper @@ -94,7 +95,7 @@ class ComposeViewModel @Inject constructor( val media: MutableStateFlow> = MutableStateFlow(emptyList()) val uploadError = MutableSharedFlow(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - lateinit var composeKind: ComposeActivity.ComposeKind + lateinit var composeKind: ComposeKind // Used in ComposeActivity to pass state to result function when cropImage contract inflight var cropImageItemOld: QueuedMedia? = null @@ -213,7 +214,28 @@ class ComposeViewModel @Inject constructor( this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true } - fun didChange(content: String?, contentWarning: String?): Boolean { + fun handleCloseButton(contentText: String?, contentWarning: String?): ConfirmationKind { + return if (didChange(contentText, contentWarning)) { + when (composeKind) { + ComposeKind.NEW -> if (isEmpty(contentText, contentWarning)) { + ConfirmationKind.NONE + } else { + ConfirmationKind.SAVE_OR_DISCARD + } + ComposeKind.EDIT_DRAFT -> if (isEmpty(contentText, contentWarning)) { + ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_DRAFT + } else { + ConfirmationKind.UPDATE_OR_DISCARD + } + ComposeKind.EDIT_POSTED -> ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_CHANGES + ComposeKind.EDIT_SCHEDULED -> ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_CHANGES + } + } else { + ConfirmationKind.NONE + } + } + + private fun didChange(content: String?, contentWarning: String?): Boolean { val textChanged = content.orEmpty() != startingText.orEmpty() val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning val mediaChanged = media.value.isNotEmpty() @@ -223,6 +245,10 @@ class ComposeViewModel @Inject constructor( return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged || didScheduledTimeChange } + private fun isEmpty(content: String?, contentWarning: String?): Boolean { + return !modifiedInitialState && (content.isNullOrBlank() && contentWarning.isNullOrBlank() && media.value.isEmpty() && poll.value == null) + } + fun contentWarningChanged(value: Boolean) { showContentWarning.value = value contentWarningStateChanged = true @@ -390,7 +416,7 @@ class ComposeViewModel @Inject constructor( return } - composeKind = composeOptions?.kind ?: ComposeActivity.ComposeKind.NEW + composeKind = composeOptions?.kind ?: ComposeKind.NEW val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy @@ -486,6 +512,14 @@ class ComposeViewModel @Inject constructor( private companion object { const val TAG = "ComposeViewModel" } + + enum class ConfirmationKind { + NONE, // just close + SAVE_OR_DISCARD, + UPDATE_OR_DISCARD, + CONTINUE_EDITING_OR_DISCARD_CHANGES, // editing post + CONTINUE_EDITING_OR_DISCARD_DRAFT // edit draft + } } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 77afc109..5abb052d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -481,6 +481,7 @@ Remove Lock account Requires you to manually approve followers + Delete draft? Save draft? Save draft? (Attachments will be uploaded again when you restore the draft.) You have unsaved changes.