mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Preclude cancelation of pre-uploaded video attachments.
Addresses ##10225.
This commit is contained in:
@@ -166,7 +166,7 @@ public class MediaUploadRepository {
|
||||
PreUploadResult result = uploadResults.get(media);
|
||||
|
||||
if (result != null) {
|
||||
Log.d(TAG, "Canceling upload jobs for " + result.getJobIds().size() + " media items.");
|
||||
Log.d(TAG, "Canceling attachment upload job for " + result.getAttachmentId());
|
||||
Stream.of(result.getJobIds()).forEach(jobManager::cancel);
|
||||
uploadResults.remove(media);
|
||||
SignalDatabase.attachments().deleteAttachment(result.getAttachmentId());
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.video.TranscodingPreset
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
data class MediaSelectionState(
|
||||
val sendType: MessageSendType,
|
||||
@@ -44,6 +45,14 @@ data class MediaSelectionState(
|
||||
return editorStateMap[uri] as? VideoTrimData ?: VideoTrimData()
|
||||
}
|
||||
|
||||
fun calculateMaxVideoDurationUs(maxFileSize: Long): Long {
|
||||
return if (isStory && !MediaConstraints.isVideoTranscodeAvailable()) {
|
||||
Stories.MAX_VIDEO_DURATION_MILLIS
|
||||
} else {
|
||||
transcodingPreset.calculateMaxVideoUploadDurationInSeconds(maxFileSize).seconds.inWholeMicroseconds
|
||||
}
|
||||
}
|
||||
|
||||
enum class ViewOnceToggleState(val code: Int) {
|
||||
INFINITE(0),
|
||||
ONCE(1);
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import java.util.Collections
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* ViewModel which maintains the list of selected media and other shared values.
|
||||
@@ -127,7 +126,7 @@ class MediaSelectionViewModel(
|
||||
}
|
||||
|
||||
if (initialMedia.isNotEmpty()) {
|
||||
addMedia(initialMedia)
|
||||
addMedia(initialMedia.toSet())
|
||||
}
|
||||
|
||||
disposables += selectedMediaSubject
|
||||
@@ -165,7 +164,7 @@ class MediaSelectionViewModel(
|
||||
}
|
||||
|
||||
fun addMedia(media: Media) {
|
||||
addMedia(listOf(media))
|
||||
addMedia(setOf(media))
|
||||
}
|
||||
|
||||
fun isStory(): Boolean {
|
||||
@@ -176,7 +175,7 @@ class MediaSelectionViewModel(
|
||||
return store.state.storySendRequirements
|
||||
}
|
||||
|
||||
private fun addMedia(media: List<Media>) {
|
||||
private fun addMedia(media: Set<Media>) {
|
||||
val newSelectionList: List<Media> = linkedSetOf<Media>().apply {
|
||||
addAll(store.state.selectedMedia)
|
||||
addAll(media)
|
||||
@@ -188,11 +187,16 @@ class MediaSelectionViewModel(
|
||||
.subscribe { filterResult ->
|
||||
if (filterResult.filteredMedia.isNotEmpty()) {
|
||||
store.update {
|
||||
val maxDuration = it.calculateMaxVideoDurationUs(getMediaConstraints().getVideoMaxSize())
|
||||
val initializedVideoEditorStates = filterResult.filteredMedia.filterNot { media -> it.editorStateMap.containsKey(media.uri) }
|
||||
.filter { media -> MediaUtil.isNonGifVideo(media) }
|
||||
.associate { video: Media ->
|
||||
val duration = video.duration.milliseconds.inWholeMicroseconds
|
||||
video.uri to VideoTrimData(false, duration, 0, duration)
|
||||
if (duration < maxDuration) {
|
||||
video.uri to VideoTrimData(false, duration, 0, duration)
|
||||
} else {
|
||||
video.uri to VideoTrimData(true, duration, 0, maxDuration)
|
||||
}
|
||||
}
|
||||
it.copy(
|
||||
selectedMedia = filterResult.filteredMedia,
|
||||
@@ -341,7 +345,7 @@ class MediaSelectionViewModel(
|
||||
store.update { it.copy(viewOnceToggleState = it.viewOnceToggleState.next()) }
|
||||
}
|
||||
|
||||
fun onEditVideoDuration(context: Context, totalDurationUs: Long, startTimeUs: Long, endTimeUs: Long, touchEnabled: Boolean) {
|
||||
fun onEditVideoDuration(totalDurationUs: Long, startTimeUs: Long, endTimeUs: Long, touchEnabled: Boolean) {
|
||||
store.update {
|
||||
val uri = it.focusedMedia?.uri ?: return@update it
|
||||
val data = it.getOrCreateVideoTrimData(uri)
|
||||
@@ -351,27 +355,30 @@ class MediaSelectionViewModel(
|
||||
val durationEdited = clampedStartTime > 0 || endTimeUs < totalDurationUs
|
||||
val isEntireDuration = startTimeUs == 0L && endTimeUs == totalDurationUs
|
||||
val endMoved = !isEntireDuration && data.endTimeUs != endTimeUs
|
||||
val maxVideoDurationUs: Long = if (it.isStory && !MediaConstraints.isVideoTranscodeAvailable()) {
|
||||
Stories.MAX_VIDEO_DURATION_MILLIS
|
||||
} else {
|
||||
it.transcodingPreset.calculateMaxVideoUploadDurationInSeconds(getMediaConstraints().getVideoMaxSize(context)).seconds.inWholeMicroseconds
|
||||
}
|
||||
val maxVideoDurationUs: Long = it.calculateMaxVideoDurationUs(getMediaConstraints().getVideoMaxSize())
|
||||
val preserveStartTime = unedited || !endMoved
|
||||
val videoTrimData = VideoTrimData(durationEdited, totalDurationUs, clampedStartTime, endTimeUs)
|
||||
val updatedData = clampToMaxClipDuration(videoTrimData, maxVideoDurationUs, preserveStartTime)
|
||||
|
||||
if (updatedData != videoTrimData) {
|
||||
Log.d(TAG, "Video trim clamped from ${videoTrimData.startTimeUs}, ${videoTrimData.endTimeUs} to ${updatedData.startTimeUs}, ${updatedData.endTimeUs}")
|
||||
Log.d(TAG, "Video attachment trim clamped from ${videoTrimData.startTimeUs}, ${videoTrimData.endTimeUs} to ${updatedData.startTimeUs}, ${updatedData.endTimeUs}")
|
||||
}
|
||||
|
||||
if (unedited && durationEdited) {
|
||||
Log.d(TAG, "Canceling upload because the duration has been edited for the first time..")
|
||||
Log.d(TAG, "Canceling attachment upload because the duration has been edited for the first time..")
|
||||
cancelUpload(MediaBuilder.buildMedia(uri))
|
||||
}
|
||||
it.copy(
|
||||
isTouchEnabled = touchEnabled,
|
||||
editorStateMap = it.editorStateMap + (uri to updatedData)
|
||||
)
|
||||
|
||||
if (updatedData != data) {
|
||||
Log.d(TAG, "Updating video attachment trim data for $uri")
|
||||
it.copy(
|
||||
isTouchEnabled = touchEnabled,
|
||||
editorStateMap = it.editorStateMap + (uri to updatedData)
|
||||
)
|
||||
} else {
|
||||
Log.d(TAG, "Preserving video attachment trim data for $uri")
|
||||
it.copy(isTouchEnabled = touchEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,8 +114,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
private var disposables: LifecycleDisposable = LifecycleDisposable()
|
||||
private var sentMediaQuality: SentMediaQuality = SignalStore.settings.sentMediaQuality
|
||||
private var viewOnceToggleState: MediaSelectionState.ViewOnceToggleState = MediaSelectionState.ViewOnceToggleState.default
|
||||
|
||||
private var scheduledSendTime: Long? = null
|
||||
private var readyToSend = true
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
postponeEnterTransition()
|
||||
@@ -202,6 +202,14 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
sendButton.setOnClickListener {
|
||||
if (!readyToSend) {
|
||||
Log.d(TAG, "Attachment send button not currently enabled. Ignoring click event.")
|
||||
return@setOnClickListener
|
||||
} else {
|
||||
Log.d(TAG, "Attachment send button enabled. Processing click event.")
|
||||
readyToSend = false
|
||||
}
|
||||
|
||||
val viewOnce: Boolean = sharedViewModel.state.value?.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE
|
||||
|
||||
if (sharedViewModel.isContactSelectionRequired) {
|
||||
@@ -216,7 +224,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
val snapshot = sharedViewModel.state.value
|
||||
|
||||
if (snapshot != null) {
|
||||
sendButton.isEnabled = false
|
||||
readyToSend = false
|
||||
SimpleTask.run(viewLifecycleOwner.lifecycle, {
|
||||
snapshot.selectedMedia.take(2).map { media ->
|
||||
val editorData = snapshot.editorStateMap[media.uri]
|
||||
@@ -232,7 +240,6 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
}
|
||||
}, {
|
||||
sendButton.isEnabled = true
|
||||
storiesLauncher.launch(StoriesMultiselectForwardActivity.Args(args, it))
|
||||
})
|
||||
} else {
|
||||
@@ -249,7 +256,15 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
Log.d(TAG, "Performing send add to group story dialog.")
|
||||
performSend()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
readyToSend = true
|
||||
}
|
||||
.setOnCancelListener {
|
||||
readyToSend = true
|
||||
}
|
||||
.setOnDismissListener {
|
||||
readyToSend = true
|
||||
}
|
||||
.show()
|
||||
scheduledSendTime = null
|
||||
} else {
|
||||
@@ -325,7 +340,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
state.selectedMedia.map { MediaReviewSelectedItem.Model(it, state.focusedMedia == it) } + MediaReviewAddItem.Model
|
||||
)
|
||||
|
||||
presentSendButton(state.sendType, state.recipient)
|
||||
presentSendButton(readyToSend, state.sendType, state.recipient)
|
||||
presentPager(state)
|
||||
presentAddMessageEntry(state.viewOnceToggleState, state.message)
|
||||
presentImageQualityToggle(state)
|
||||
@@ -449,6 +464,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
private fun performSend(selection: List<ContactSearchKey> = listOf()) {
|
||||
Log.d(TAG, "Performing attachment send.")
|
||||
readyToSend = false
|
||||
progressWrapper.visible = true
|
||||
progressWrapper.animate()
|
||||
.setStartDelay(300)
|
||||
@@ -456,11 +473,20 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
.alpha(1f)
|
||||
|
||||
disposables += sharedViewModel
|
||||
.send(selection.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java), scheduledSendTime)
|
||||
.send(selection.filterIsInstance<ContactSearchKey.RecipientSearchKey>(), scheduledSendTime)
|
||||
.subscribe(
|
||||
{ result -> callback.onSentWithResult(result) },
|
||||
{ error -> callback.onSendError(error) },
|
||||
{ callback.onSentWithoutResult() }
|
||||
{ result ->
|
||||
callback.onSentWithResult(result)
|
||||
readyToSend = true
|
||||
},
|
||||
{ error ->
|
||||
callback.onSendError(error)
|
||||
readyToSend = true
|
||||
},
|
||||
{
|
||||
callback.onSentWithoutResult()
|
||||
readyToSend = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -500,8 +526,9 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
)
|
||||
}
|
||||
|
||||
private fun presentSendButton(sendType: MessageSendType, recipient: Recipient?) {
|
||||
private fun presentSendButton(enabled: Boolean, sendType: MessageSendType, recipient: Recipient?) {
|
||||
val sendButtonBackgroundTint = when {
|
||||
!enabled -> ContextCompat.getColor(requireContext(), R.color.core_grey_50)
|
||||
recipient != null -> recipient.chatColors.asSingleColor()
|
||||
sendType.usesSignalTransport -> ContextCompat.getColor(requireContext(), R.color.signal_colorOnSecondaryContainer)
|
||||
else -> ContextCompat.getColor(requireContext(), R.color.core_grey_50)
|
||||
@@ -513,6 +540,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
val sendButtonForegroundTint = when {
|
||||
!enabled -> ContextCompat.getColor(requireContext(), R.color.signal_colorSecondaryContainer)
|
||||
recipient != null -> ContextCompat.getColor(requireContext(), R.color.signal_colorOnCustom)
|
||||
else -> ContextCompat.getColor(requireContext(), R.color.signal_colorSecondaryContainer)
|
||||
}
|
||||
@@ -549,7 +577,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
videoTimeLine.unregisterDragListener()
|
||||
}
|
||||
val size: Long = tryGetUriSize(requireContext(), uri, Long.MAX_VALUE)
|
||||
val maxSend = sharedViewModel.getMediaConstraints().getVideoMaxSize(requireContext())
|
||||
val maxSend = sharedViewModel.getMediaConstraints().getVideoMaxSize()
|
||||
if (size > maxSend) {
|
||||
videoTimeLine.setTimeLimit(state.transcodingPreset.calculateMaxVideoUploadDurationInSeconds(maxSend), TimeUnit.SECONDS)
|
||||
}
|
||||
@@ -791,6 +819,6 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
override fun onRangeDrag(minValue: Long, maxValue: Long, duration: Long, end: Boolean) {
|
||||
sharedViewModel.onEditVideoDuration(context = requireContext(), totalDurationUs = duration, startTimeUs = minValue, endTimeUs = maxValue, touchEnabled = end)
|
||||
sharedViewModel.onEditVideoDuration(totalDurationUs = duration, startTimeUs = minValue, endTimeUs = maxValue, touchEnabled = end)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class MediaReviewVideoPageFragment : Fragment(R.layout.fragment_container), Vide
|
||||
}
|
||||
|
||||
private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelableCompat(ARG_URI, Uri::class.java))
|
||||
private fun requireMaxAttachmentSize(): Long = sharedViewModel.getMediaConstraints().getVideoMaxSize(requireContext())
|
||||
private fun requireMaxAttachmentSize(): Long = sharedViewModel.getMediaConstraints().getVideoMaxSize()
|
||||
private fun requireIsVideoGif(): Boolean = requireNotNull(requireArguments().getBoolean(ARG_IS_VIDEO_GIF))
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user