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. ---------
This commit is contained in:
parent
eedf6abf91
commit
485a4c364e
3 changed files with 69 additions and 13 deletions
|
@ -78,6 +78,7 @@ import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.adapter.EmojiAdapter
|
import com.keylesspalace.tusky.adapter.EmojiAdapter
|
||||||
import com.keylesspalace.tusky.adapter.LocaleAdapter
|
import com.keylesspalace.tusky.adapter.LocaleAdapter
|
||||||
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
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.CaptionDialog
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
|
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||||
|
@ -1126,16 +1127,19 @@ class ComposeActivity :
|
||||||
private fun handleCloseButton() {
|
private fun handleCloseButton() {
|
||||||
val contentText = binding.composeEditField.text.toString()
|
val contentText = binding.composeEditField.text.toString()
|
||||||
val contentWarning = binding.composeContentWarningField.text.toString()
|
val contentWarning = binding.composeContentWarningField.text.toString()
|
||||||
if (viewModel.didChange(contentText, contentWarning)) {
|
when (viewModel.handleCloseButton(contentText, contentWarning)) {
|
||||||
when (viewModel.composeKind) {
|
ConfirmationKind.NONE -> {
|
||||||
ComposeKind.NEW -> getSaveAsDraftOrDiscardDialog(contentText, contentWarning)
|
viewModel.stopUploads()
|
||||||
ComposeKind.EDIT_DRAFT -> getUpdateDraftOrDiscardDialog(contentText, contentWarning)
|
finishWithoutSlideOutAnimation()
|
||||||
ComposeKind.EDIT_POSTED -> getContinueEditingOrDiscardDialog()
|
}
|
||||||
ComposeKind.EDIT_SCHEDULED -> getContinueEditingOrDiscardDialog()
|
ConfirmationKind.SAVE_OR_DISCARD ->
|
||||||
}.show()
|
getSaveAsDraftOrDiscardDialog(contentText, contentWarning).show()
|
||||||
} else {
|
ConfirmationKind.UPDATE_OR_DISCARD ->
|
||||||
viewModel.stopUploads()
|
getUpdateDraftOrDiscardDialog(contentText, contentWarning).show()
|
||||||
finishWithoutSlideOutAnimation()
|
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() {
|
private fun deleteDraftAndFinish() {
|
||||||
viewModel.deleteDraft()
|
viewModel.deleteDraft()
|
||||||
finishWithoutSlideOutAnimation()
|
finishWithoutSlideOutAnimation()
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
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.ComposeActivity.QueuedMedia
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult
|
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult
|
||||||
import com.keylesspalace.tusky.components.drafts.DraftHelper
|
import com.keylesspalace.tusky.components.drafts.DraftHelper
|
||||||
|
@ -94,7 +95,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
|
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
|
||||||
val uploadError = MutableSharedFlow<Throwable>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
val uploadError = MutableSharedFlow<Throwable>(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
|
// Used in ComposeActivity to pass state to result function when cropImage contract inflight
|
||||||
var cropImageItemOld: QueuedMedia? = null
|
var cropImageItemOld: QueuedMedia? = null
|
||||||
|
@ -213,7 +214,28 @@ class ComposeViewModel @Inject constructor(
|
||||||
this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true
|
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 textChanged = content.orEmpty() != startingText.orEmpty()
|
||||||
val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning
|
val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning
|
||||||
val mediaChanged = media.value.isNotEmpty()
|
val mediaChanged = media.value.isNotEmpty()
|
||||||
|
@ -223,6 +245,10 @@ class ComposeViewModel @Inject constructor(
|
||||||
return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged || didScheduledTimeChange
|
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) {
|
fun contentWarningChanged(value: Boolean) {
|
||||||
showContentWarning.value = value
|
showContentWarning.value = value
|
||||||
contentWarningStateChanged = true
|
contentWarningStateChanged = true
|
||||||
|
@ -390,7 +416,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
composeKind = composeOptions?.kind ?: ComposeActivity.ComposeKind.NEW
|
composeKind = composeOptions?.kind ?: ComposeKind.NEW
|
||||||
|
|
||||||
val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy
|
val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy
|
||||||
|
|
||||||
|
@ -486,6 +512,14 @@ class ComposeViewModel @Inject constructor(
|
||||||
private companion object {
|
private companion object {
|
||||||
const val TAG = "ComposeViewModel"
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -481,6 +481,7 @@
|
||||||
<string name="action_remove">Remove</string>
|
<string name="action_remove">Remove</string>
|
||||||
<string name="lock_account_label">Lock account</string>
|
<string name="lock_account_label">Lock account</string>
|
||||||
<string name="lock_account_label_description">Requires you to manually approve followers</string>
|
<string name="lock_account_label_description">Requires you to manually approve followers</string>
|
||||||
|
<string name="compose_delete_draft">Delete draft?</string>
|
||||||
<string name="compose_save_draft">Save draft?</string>
|
<string name="compose_save_draft">Save draft?</string>
|
||||||
<string name="compose_save_draft_loses_media">Save draft? (Attachments will be uploaded again when you restore the draft.)</string>
|
<string name="compose_save_draft_loses_media">Save draft? (Attachments will be uploaded again when you restore the draft.)</string>
|
||||||
<string name="compose_unsaved_changes">You have unsaved changes.</string>
|
<string name="compose_unsaved_changes">You have unsaved changes.</string>
|
||||||
|
|
Loading…
Reference in a new issue