diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt index 0d3e05cb3..b46d6124e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt @@ -811,9 +811,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun mention() { loadedAccount?.let { val options = if (viewModel.isSelf) { - ComposeActivity.ComposeOptions() + ComposeActivity.ComposeOptions(kind = ComposeActivity.ComposeKind.NEW) } else { - ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username)) + ComposeActivity.ComposeOptions( + mentionedUsernames = setOf(it.username), + kind = ComposeActivity.ComposeKind.NEW + ) } val intent = ComposeActivity.startIntent(this, options) startActivity(intent) 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 5f708b829..2fb2ff9a3 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 @@ -1115,30 +1115,79 @@ class ComposeActivity : val contentText = binding.composeEditField.text.toString() val contentWarning = binding.composeContentWarningField.text.toString() if (viewModel.didChange(contentText, contentWarning)) { - - val warning = if (!viewModel.media.value.isEmpty()) { - R.string.compose_save_draft_loses_media - } else { - R.string.compose_save_draft - } - - AlertDialog.Builder(this) - .setMessage(warning) - .setPositiveButton(R.string.action_save) { _, _ -> - viewModel.stopUploads() - saveDraftAndFinish(contentText, contentWarning) - } - .setNegativeButton(R.string.action_delete) { _, _ -> - viewModel.stopUploads() - deleteDraftAndFinish() - } - .show() + 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() } } + /** + * User is editing a new post, and can either save the changes as a draft or discard them. + */ + private fun getSaveAsDraftOrDiscardDialog(contentText: String, contentWarning: String): AlertDialog.Builder { + val warning = if (viewModel.media.value.isNotEmpty()) { + R.string.compose_save_draft_loses_media + } else { + R.string.compose_save_draft + } + + return AlertDialog.Builder(this) + .setMessage(warning) + .setPositiveButton(R.string.action_save) { _, _ -> + viewModel.stopUploads() + saveDraftAndFinish(contentText, contentWarning) + } + .setNegativeButton(R.string.action_delete) { _, _ -> + viewModel.stopUploads() + deleteDraftAndFinish() + } + } + + /** + * User is editing an existing draft, and can either update the draft with the new changes or + * discard them. + */ + private fun getUpdateDraftOrDiscardDialog(contentText: String, contentWarning: String): AlertDialog.Builder { + val warning = if (viewModel.media.value.isNotEmpty()) { + R.string.compose_save_draft_loses_media + } else { + R.string.compose_save_draft + } + + return AlertDialog.Builder(this) + .setMessage(warning) + .setPositiveButton(R.string.action_save) { _, _ -> + viewModel.stopUploads() + saveDraftAndFinish(contentText, contentWarning) + } + .setNegativeButton(R.string.action_discard) { _, _ -> + viewModel.stopUploads() + finishWithoutSlideOutAnimation() + } + } + + /** + * User is editing a post (scheduled, or posted), and can either go back to editing, or + * discard the changes. + */ + private fun getContinueEditingOrDiscardDialog(): AlertDialog.Builder { + return AlertDialog.Builder(this) + .setMessage(R.string.compose_unsaved_changes) + .setPositiveButton(R.string.action_continue_edit) { _, _ -> + // Do nothing, dialog will dismiss, user can continue editing + } + .setNegativeButton(R.string.action_discard) { _, _ -> + viewModel.stopUploads() + finishWithoutSlideOutAnimation() + } + } + private fun deleteDraftAndFinish() { viewModel.deleteDraft() finishWithoutSlideOutAnimation() @@ -1217,6 +1266,24 @@ class ComposeActivity : } } + /** + * Status' kind. This particularly affects how the status is handled if the user + * backs out of the edit. + */ + enum class ComposeKind { + /** Status is new */ + NEW, + + /** Editing a posted status */ + EDIT_POSTED, + + /** Editing a status started as an existing draft */ + EDIT_DRAFT, + + /** Editing an an existing scheduled status */ + EDIT_SCHEDULED + } + @Parcelize data class ComposeOptions( // Let's keep fields var until all consumers are Kotlin @@ -1240,6 +1307,7 @@ class ComposeActivity : var modifiedInitialState: Boolean? = null, var language: String? = null, var statusId: String? = null, + var kind: ComposeKind? = null ) : Parcelable companion object { 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 e697fad65..75cad5297 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 @@ -95,6 +95,8 @@ 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 + // Used in ComposeActivity to pass state to result function when cropImage contract inflight var cropImageItemOld: QueuedMedia? = null @@ -213,15 +215,8 @@ class ComposeViewModel @Inject constructor( } fun didChange(content: String?, contentWarning: String?): Boolean { - - val textChanged = !( - content.isNullOrEmpty() || - startingText?.startsWith(content.toString()) ?: false - ) - - val contentWarningChanged = showContentWarning.value && - !contentWarning.isNullOrEmpty() && - !startingContentWarning.startsWith(contentWarning.toString()) + val textChanged = content.orEmpty() != startingText.orEmpty() + val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning val mediaChanged = media.value.isNotEmpty() val pollChanged = poll.value != null val didScheduledTimeChange = hasScheduledTimeChanged @@ -411,6 +406,8 @@ class ComposeViewModel @Inject constructor( return } + composeKind = composeOptions?.kind ?: ComposeActivity.ComposeKind.NEW + val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt index ba0c215d1..7cdcd2597 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -112,6 +112,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener { scheduledAt = draft.scheduledAt, language = draft.language, statusId = draft.statusId, + kind = ComposeActivity.ComposeKind.EDIT_DRAFT ) bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN @@ -149,6 +150,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener { scheduledAt = draft.scheduledAt, language = draft.language, statusId = draft.statusId, + kind = ComposeActivity.ComposeKind.EDIT_DRAFT ) startActivity(ComposeActivity.startIntent(this, composeOptions)) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java index af70cce2e..41853cc1f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java @@ -371,6 +371,7 @@ public class NotificationHelper { composeOptions.setMentionedUsernames(mentionedUsernames); composeOptions.setModifiedInitialState(true); composeOptions.setLanguage(actionableStatus.getLanguage()); + composeOptions.setKind(ComposeActivity.ComposeKind.NEW); Intent composeIntent = ComposeActivity.startIntent( context, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt index 43ce346d6..ade93322f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt @@ -128,7 +128,8 @@ class ScheduledStatusActivity : BaseActivity(), ScheduledStatusActionListener, I inReplyToId = item.params.inReplyToId, visibility = item.params.visibility, scheduledAt = item.scheduledAt, - sensitive = item.params.sensitive + sensitive = item.params.sensitive, + kind = ComposeActivity.ComposeKind.EDIT_SCHEDULED ) ) startActivity(intent) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index b261ce311..103bcfc53 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -223,6 +223,7 @@ class SearchStatusesFragment : SearchFragment(), Status replyingStatusAuthor = actionableStatus.account.localUsername, replyingStatusContent = status.content.toString(), language = actionableStatus.language, + kind = ComposeActivity.ComposeKind.NEW ) ) bottomSheetActivity?.startActivityWithSlideInAnimation(intent) @@ -481,6 +482,7 @@ class SearchStatusesFragment : SearchFragment(), Status sensitive = redraftStatus.sensitive, poll = redraftStatus.poll?.toNewPoll(status.createdAt), language = redraftStatus.language, + kind = ComposeActivity.ComposeKind.NEW ) ) startActivity(intent) @@ -510,6 +512,7 @@ class SearchStatusesFragment : SearchFragment(), Status language = status.language, statusId = source.id, poll = status.poll?.toNewPoll(status.createdAt), + kind = ComposeActivity.ComposeKind.EDIT_POSTED, ) startActivity(ComposeActivity.startIntent(requireContext(), composeOptions)) }, diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt index 9b217f84f..32808cced 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt @@ -45,6 +45,7 @@ import com.keylesspalace.tusky.PostLookupFallbackBehavior import com.keylesspalace.tusky.R import com.keylesspalace.tusky.StatusListActivity.Companion.newHashtagIntent import com.keylesspalace.tusky.ViewMediaActivity.Companion.newIntent +import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.startIntent import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions import com.keylesspalace.tusky.components.report.ReportActivity.Companion.getIntent @@ -135,6 +136,7 @@ abstract class SFragment : Fragment(), Injectable { replyingStatusAuthor = account.localUsername, replyingStatusContent = actionableStatus.content.parseAsMastodonHtml().toString(), language = actionableStatus.language, + kind = ComposeActivity.ComposeKind.NEW ) val intent = startIntent(requireContext(), composeOptions) @@ -411,6 +413,7 @@ abstract class SFragment : Fragment(), Injectable { modifiedInitialState = true, language = sourceStatus.language, poll = sourceStatus.poll?.toNewPoll(sourceStatus.createdAt), + kind = ComposeActivity.ComposeKind.NEW ) startActivity(startIntent(requireContext(), composeOptions)) } @@ -437,6 +440,7 @@ abstract class SFragment : Fragment(), Injectable { language = status.language, statusId = source.id, poll = status.poll?.toNewPoll(status.createdAt), + kind = ComposeActivity.ComposeKind.EDIT_POSTED ) startActivity(startIntent(requireContext(), composeOptions)) }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6fcc16b06..2a66137fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,6 +109,8 @@ Delete Delete conversation Delete and re-draft + Discard changes + Continue editing TOOT TOOT! Retry @@ -443,6 +445,7 @@ Requires you to manually approve followers Save draft? Save draft? (Attachments will be uploaded again when you restore the draft.) + You have unsaved changes. Sending post… Error sending post Sending Posts