Delete scheduled post after editing (#1947)
* Delete scheduled post after editing This ensures that editing a scheduled toot will not duplicate it. Fixes #1924 * Fix scheduled toot editing * Convert isEditingScheduledToot to property * Replace MediatorLiveData with combineLiveData * Remove dead code * Fix observer emission * Fix "Finish media upload" appearing for split second
This commit is contained in:
parent
8b3c9a8f09
commit
313209e249
|
@ -164,6 +164,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd
|
||||||
List<String> descriptions = gson.fromJson(item.getDescriptions(), stringListType);
|
List<String> descriptions = gson.fromJson(item.getDescriptions(), stringListType);
|
||||||
|
|
||||||
ComposeOptions composeOptions = new ComposeOptions(
|
ComposeOptions composeOptions = new ComposeOptions(
|
||||||
|
/*scheduledTootUid*/null,
|
||||||
item.getUid(),
|
item.getUid(),
|
||||||
item.getText(),
|
item.getText(),
|
||||||
jsonUrls,
|
jsonUrls,
|
||||||
|
|
|
@ -1007,9 +1007,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
composeEditField.error = getString(R.string.error_empty)
|
composeEditField.error = getString(R.string.error_empty)
|
||||||
enableButtons(true)
|
enableButtons(true)
|
||||||
} else if (characterCount <= maximumTootCharacters) {
|
} else if (characterCount <= maximumTootCharacters) {
|
||||||
finishingUploadDialog = ProgressDialog.show(
|
if (viewModel.media.value!!.isNotEmpty()) {
|
||||||
this, getString(R.string.dialog_title_finishing_media_upload),
|
finishingUploadDialog = ProgressDialog.show(
|
||||||
getString(R.string.dialog_message_uploading_media), true, true)
|
this, getString(R.string.dialog_title_finishing_media_upload),
|
||||||
|
getString(R.string.dialog_message_uploading_media), true, true)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.sendStatus(contentText, spoilerText, preview).observeOnce(this) {
|
viewModel.sendStatus(contentText, spoilerText, preview).observeOnce(this) {
|
||||||
finishingUploadDialog?.dismiss()
|
finishingUploadDialog?.dismiss()
|
||||||
|
@ -1311,6 +1313,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ComposeOptions(
|
data class ComposeOptions(
|
||||||
// Let's keep fields var until all consumers are Kotlin
|
// Let's keep fields var until all consumers are Kotlin
|
||||||
|
var scheduledTootUid: String? = null,
|
||||||
var savedTootUid: Int? = null,
|
var savedTootUid: Int? = null,
|
||||||
var tootText: String? = null,
|
var tootText: String? = null,
|
||||||
var mediaUrls: List<String>? = null,
|
var mediaUrls: List<String>? = null,
|
||||||
|
|
|
@ -19,8 +19,10 @@ import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.work.impl.utils.LiveDataUtils
|
||||||
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter
|
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter
|
||||||
import com.keylesspalace.tusky.components.common.CommonComposeViewModel
|
import com.keylesspalace.tusky.components.common.CommonComposeViewModel
|
||||||
import com.keylesspalace.tusky.components.common.MediaUploader
|
import com.keylesspalace.tusky.components.common.MediaUploader
|
||||||
|
@ -38,6 +40,8 @@ import com.keylesspalace.tusky.service.ServiceClient
|
||||||
import com.keylesspalace.tusky.service.TootToSend
|
import com.keylesspalace.tusky.service.TootToSend
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.*
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.Observable.empty
|
||||||
|
import io.reactivex.Observable.just
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.rxkotlin.Singles
|
import io.reactivex.rxkotlin.Singles
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
@ -58,6 +62,7 @@ class ComposeViewModel
|
||||||
private var replyingStatusContent: String? = null
|
private var replyingStatusContent: String? = null
|
||||||
internal var startingText: String? = null
|
internal var startingText: String? = null
|
||||||
private var savedTootUid: Int = 0
|
private var savedTootUid: Int = 0
|
||||||
|
private var scheduledTootUid: String? = null
|
||||||
private var startingContentWarning: String = ""
|
private var startingContentWarning: String = ""
|
||||||
private var inReplyToId: String? = null
|
private var inReplyToId: String? = null
|
||||||
private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN
|
private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN
|
||||||
|
@ -78,6 +83,105 @@ class ComposeViewModel
|
||||||
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
|
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
|
||||||
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
|
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
|
||||||
|
|
||||||
|
val media = mutableLiveData<List<QueuedMedia>>(listOf())
|
||||||
|
val uploadError = MutableLiveData<Throwable>()
|
||||||
|
|
||||||
|
private val mediaToDisposable = mutableMapOf<Long, Disposable>()
|
||||||
|
|
||||||
|
private val isEditingScheduledToot get() = !scheduledTootUid.isNullOrEmpty()
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
Singles.zip(api.getCustomEmojis(), api.getInstance()) { emojis, instance ->
|
||||||
|
InstanceEntity(
|
||||||
|
instance = accountManager.activeAccount?.domain!!,
|
||||||
|
emojiList = emojis,
|
||||||
|
maximumTootCharacters = instance.maxTootChars,
|
||||||
|
maxPollOptions = instance.pollLimits?.maxOptions,
|
||||||
|
maxPollOptionLength = instance.pollLimits?.maxOptionChars,
|
||||||
|
version = instance.version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.doOnSuccess {
|
||||||
|
db.instanceDao().insertOrReplace(it)
|
||||||
|
}
|
||||||
|
.onErrorResumeNext(
|
||||||
|
db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
||||||
|
)
|
||||||
|
.subscribe ({ instanceEntity ->
|
||||||
|
emoji.postValue(instanceEntity.emojiList)
|
||||||
|
instance.postValue(instanceEntity)
|
||||||
|
}, { throwable ->
|
||||||
|
// this can happen on network error when no cached data is available
|
||||||
|
Log.w(TAG, "error loading instance data", throwable)
|
||||||
|
})
|
||||||
|
.autoDispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pickMedia(uri: Uri): LiveData<Either<Throwable, QueuedMedia>> {
|
||||||
|
// We are not calling .toLiveData() here because we don't want to stop the process when
|
||||||
|
// the Activity goes away temporarily (like on screen rotation).
|
||||||
|
val liveData = MutableLiveData<Either<Throwable, QueuedMedia>>()
|
||||||
|
mediaUploader.prepareMedia(uri)
|
||||||
|
.map { (type, uri, size) ->
|
||||||
|
val mediaItems = media.value!!
|
||||||
|
if (type != QueuedMedia.Type.IMAGE
|
||||||
|
&& mediaItems.isNotEmpty()
|
||||||
|
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) {
|
||||||
|
throw VideoOrImageException()
|
||||||
|
} else {
|
||||||
|
addMediaToQueue(type, uri, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribe({ queuedMedia ->
|
||||||
|
liveData.postValue(Either.Right(queuedMedia))
|
||||||
|
}, { error ->
|
||||||
|
liveData.postValue(Either.Left(error))
|
||||||
|
})
|
||||||
|
.autoDispose()
|
||||||
|
return liveData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addMediaToQueue(type: QueuedMedia.Type, uri: Uri, mediaSize: Long): QueuedMedia {
|
||||||
|
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize)
|
||||||
|
media.value = media.value!! + mediaItem
|
||||||
|
mediaToDisposable[mediaItem.localId] = mediaUploader
|
||||||
|
.uploadMedia(mediaItem)
|
||||||
|
.subscribe ({ event ->
|
||||||
|
val item = media.value?.find { it.localId == mediaItem.localId }
|
||||||
|
?: return@subscribe
|
||||||
|
val newMediaItem = when (event) {
|
||||||
|
is UploadEvent.ProgressEvent ->
|
||||||
|
item.copy(uploadPercent = event.percentage)
|
||||||
|
is UploadEvent.FinishedEvent ->
|
||||||
|
item.copy(id = event.attachment.id, uploadPercent = -1)
|
||||||
|
}
|
||||||
|
synchronized(media) {
|
||||||
|
val mediaValue = media.value!!
|
||||||
|
val index = mediaValue.indexOfFirst { it.localId == newMediaItem.localId }
|
||||||
|
media.postValue(if (index == -1) {
|
||||||
|
mediaValue + newMediaItem
|
||||||
|
} else {
|
||||||
|
mediaValue.toMutableList().also { it[index] = newMediaItem }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, { error ->
|
||||||
|
media.postValue(media.value?.filter { it.localId != mediaItem.localId } ?: emptyList())
|
||||||
|
uploadError.postValue(error)
|
||||||
|
})
|
||||||
|
return mediaItem
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addUploadedMedia(id: String, type: QueuedMedia.Type, uri: Uri, description: String?) {
|
||||||
|
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, -1, id, description)
|
||||||
|
media.value = media.value!! + mediaItem
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeMediaFromQueue(item: QueuedMedia) {
|
||||||
|
mediaToDisposable[item.localId]?.dispose()
|
||||||
|
media.value = media.value!!.withoutFirstWhich { it.localId == item.localId }
|
||||||
|
}
|
||||||
|
|
||||||
fun didChange(content: String?, contentWarning: String?): Boolean {
|
fun didChange(content: String?, contentWarning: String?): Boolean {
|
||||||
|
|
||||||
val textChanged = !(content.isNullOrEmpty()
|
val textChanged = !(content.isNullOrEmpty()
|
||||||
|
@ -134,7 +238,14 @@ class ComposeViewModel
|
||||||
spoilerText: String,
|
spoilerText: String,
|
||||||
preview: Boolean
|
preview: Boolean
|
||||||
): LiveData<Unit> {
|
): LiveData<Unit> {
|
||||||
return media
|
|
||||||
|
val deletionObservable = if (isEditingScheduledToot) {
|
||||||
|
api.deleteScheduledStatus(scheduledTootUid.toString()).toObservable().map { Unit }
|
||||||
|
} else {
|
||||||
|
just(Unit)
|
||||||
|
}.toLiveData()
|
||||||
|
|
||||||
|
val sendObservable = media
|
||||||
.filter { items -> items.all { it.uploadPercent == -1 } }
|
.filter { items -> items.all { it.uploadPercent == -1 } }
|
||||||
.map {
|
.map {
|
||||||
val mediaIds = ArrayList<String>()
|
val mediaIds = ArrayList<String>()
|
||||||
|
@ -167,8 +278,13 @@ class ComposeViewModel
|
||||||
idempotencyKey = randomAlphanumericString(16),
|
idempotencyKey = randomAlphanumericString(16),
|
||||||
retries = 0
|
retries = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
serviceClient.sendToot(tootToSend)
|
serviceClient.sendToot(tootToSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return combineLiveData(deletionObservable, sendObservable) { _, _ -> Unit }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
@ -222,6 +338,7 @@ class ComposeViewModel
|
||||||
|
|
||||||
|
|
||||||
savedTootUid = composeOptions?.savedTootUid ?: 0
|
savedTootUid = composeOptions?.savedTootUid ?: 0
|
||||||
|
scheduledTootUid = composeOptions?.scheduledTootUid
|
||||||
startingText = composeOptions?.tootText
|
startingText = composeOptions?.tootText
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,7 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec
|
||||||
|
|
||||||
override fun edit(item: ScheduledStatus) {
|
override fun edit(item: ScheduledStatus) {
|
||||||
val intent = ComposeActivity.startIntent(this, ComposeActivity.ComposeOptions(
|
val intent = ComposeActivity.startIntent(this, ComposeActivity.ComposeOptions(
|
||||||
|
scheduledTootUid = item.id,
|
||||||
tootText = item.params.text,
|
tootText = item.params.text,
|
||||||
contentWarning = item.params.spoilerText,
|
contentWarning = item.params.spoilerText,
|
||||||
mediaAttachments = item.mediaAttachments,
|
mediaAttachments = item.mediaAttachments,
|
||||||
|
|
Loading…
Reference in New Issue