mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-05 12:55:11 +01:00
Fix story pre-upload behavior for v3 media.
This commit is contained in:
@@ -25,6 +25,15 @@ fun StorySendRequirements.toAppSendRequirements(): Stories.MediaTransform.SendRe
|
||||
StorySendRequirements.REQUIRES_CROP -> Stories.MediaTransform.SendRequirements.REQUIRES_CLIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the app-layer [Stories.MediaTransform.SendRequirements] to the feature-module [StorySendRequirements].
|
||||
*/
|
||||
fun Stories.MediaTransform.SendRequirements.toFeatureSendRequirements(): StorySendRequirements = when (this) {
|
||||
Stories.MediaTransform.SendRequirements.VALID_DURATION -> StorySendRequirements.CAN_SEND
|
||||
Stories.MediaTransform.SendRequirements.CAN_NOT_SEND -> StorySendRequirements.CAN_NOT_SEND
|
||||
Stories.MediaTransform.SendRequirements.REQUIRES_CLIP -> StorySendRequirements.REQUIRES_CROP
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a sticker pick into a [Renderer] the image editor can place.
|
||||
*/
|
||||
|
||||
@@ -253,12 +253,8 @@ object MediaSendV3Repository : MediaSendRepository {
|
||||
return MediaConstraints.isVideoTranscodeAvailable()
|
||||
}
|
||||
|
||||
override suspend fun getStorySendRequirements(media: List<Media>): StorySendRequirements = withContext(Dispatchers.IO) {
|
||||
when (Stories.MediaTransform.getSendRequirements(media)) {
|
||||
Stories.MediaTransform.SendRequirements.VALID_DURATION -> StorySendRequirements.CAN_SEND
|
||||
Stories.MediaTransform.SendRequirements.REQUIRES_CLIP -> StorySendRequirements.REQUIRES_CROP
|
||||
Stories.MediaTransform.SendRequirements.CAN_NOT_SEND -> StorySendRequirements.CAN_NOT_SEND
|
||||
}
|
||||
override suspend fun getStorySendRequirements(media: List<Media>): Map<Uri, StorySendRequirements> = withContext(Dispatchers.IO) {
|
||||
media.associate { it.uri to Stories.MediaTransform.getSendRequirements(it).toFeatureSendRequirements() }
|
||||
}
|
||||
|
||||
override suspend fun checkUntrustedIdentities(contactIds: Set<Long>, since: Long): List<Long> = withContext(Dispatchers.Default) {
|
||||
|
||||
@@ -104,9 +104,12 @@ interface MediaSendRepository {
|
||||
fun isVideoTranscodeAvailable(): Boolean
|
||||
|
||||
/**
|
||||
* Gets story send requirements for the given media.
|
||||
* Gets the story send requirement for each of the given media, keyed by URI as the selection's stable identity.
|
||||
*
|
||||
* Per-item because pre-upload eligibility is decided per media, while the UI needs the whole selection's
|
||||
* requirement. Deriving the latter from this map keeps duration probing to a single pass.
|
||||
*/
|
||||
suspend fun getStorySendRequirements(media: List<Media>): StorySendRequirements
|
||||
suspend fun getStorySendRequirements(media: List<Media>): Map<Uri, StorySendRequirements>
|
||||
|
||||
/**
|
||||
* Checks for untrusted identity records among the given contacts.
|
||||
@@ -237,3 +240,17 @@ enum class StorySendRequirements {
|
||||
/** Requires cropping before sending to stories. */
|
||||
REQUIRES_CROP
|
||||
}
|
||||
|
||||
/**
|
||||
* The strictest requirement across [this], which is how a send treats a selection: one item that cannot be sent
|
||||
* blocks the selection, and one item needing a crop makes the selection need one. Empty selections can send.
|
||||
*/
|
||||
fun Collection<StorySendRequirements>.strictest(): StorySendRequirements {
|
||||
return fold(StorySendRequirements.CAN_SEND) { left, right ->
|
||||
when {
|
||||
left == StorySendRequirements.CAN_NOT_SEND || right == StorySendRequirements.CAN_NOT_SEND -> StorySendRequirements.CAN_NOT_SEND
|
||||
left == StorySendRequirements.REQUIRES_CROP || right == StorySendRequirements.REQUIRES_CROP -> StorySendRequirements.REQUIRES_CROP
|
||||
else -> StorySendRequirements.CAN_SEND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +162,6 @@ class MediaSendViewModel(
|
||||
.distinctUntilChanged()
|
||||
|
||||
init {
|
||||
// Matches legacy behavior: VM subscribes to connectivity updates and derives
|
||||
// isPreUploadEnabled from metered state.
|
||||
viewModelScope.launch {
|
||||
isMeteredFlow.collect { metered ->
|
||||
updateState { copy(isMeteredConnection = metered, isPreUploadEnabled = shouldPreUpload(metered)) }
|
||||
@@ -607,11 +605,11 @@ class MediaSendViewModel(
|
||||
}
|
||||
|
||||
// Update story requirements
|
||||
updateStorySendRequirements(updatedMedia)
|
||||
val storySendRequirements = updateStorySendRequirements(updatedMedia)
|
||||
|
||||
// Start pre-uploads for new media
|
||||
val newMedia = updatedMedia.filter { item -> media.any { it.uri == item.uri } }
|
||||
startUpload(newMedia)
|
||||
startUpload(newMedia, storySendRequirements)
|
||||
}
|
||||
|
||||
filterResult.error?.let { onMediaFilterError(it, isSelectionEmpty = filterResult.filteredMedia.isEmpty()) }
|
||||
@@ -787,14 +785,20 @@ class MediaSendViewModel(
|
||||
|
||||
//region Pre-Upload Management
|
||||
|
||||
private fun startUpload(media: List<Media>) {
|
||||
/**
|
||||
* @param storySendRequirements Requirements for the current selection, so a story destination can skip media that
|
||||
* will be clipped at send time and uploaded as a different asset.
|
||||
*/
|
||||
private fun startUpload(media: List<Media>, storySendRequirements: Map<Uri, StorySendRequirements>) {
|
||||
val snapshot = state.value
|
||||
if (!snapshot.isPreUploadEnabled) return
|
||||
|
||||
val filteredPreUploadMedia = if (snapshot.mode is MediaSendActivityContract.Mode.SingleRecipient) {
|
||||
val isChatDestination = (snapshot.mode is MediaSendActivityContract.Mode.SingleRecipient && !snapshot.isStory) || !snapshot.storiesEnabled
|
||||
|
||||
val filteredPreUploadMedia = if (isChatDestination) {
|
||||
media.filter { !ContentTypeUtil.isDocumentType(it.contentType) }
|
||||
} else {
|
||||
media.filter { ContentTypeUtil.isStorySupportedType(it.contentType) }
|
||||
media.filter { storySendRequirements[it.uri] != StorySendRequirements.REQUIRES_CROP }
|
||||
}
|
||||
|
||||
preUploadController.startUpload(filteredPreUploadMedia, snapshot.recipientId)
|
||||
@@ -1100,9 +1104,10 @@ class MediaSendViewModel(
|
||||
* Computed for every flow, not just story flows: the contact picker consults this before allowing a story
|
||||
* to be selected, so leaving it at its default would strip story selections made mid-flow.
|
||||
*/
|
||||
private suspend fun updateStorySendRequirements(media: List<Media>) {
|
||||
private suspend fun updateStorySendRequirements(media: List<Media>): Map<Uri, StorySendRequirements> {
|
||||
val requirements = repository.getStorySendRequirements(media)
|
||||
updateState { copy(storySendRequirements = requirements) }
|
||||
updateState { copy(storySendRequirements = requirements.values.strictest()) }
|
||||
return requirements
|
||||
}
|
||||
|
||||
//endregion
|
||||
@@ -1307,7 +1312,12 @@ class MediaSendViewModel(
|
||||
preUploadController.deleteAbandonedAttachments()
|
||||
}
|
||||
|
||||
private fun shouldPreUpload(metered: Boolean): Boolean = !metered
|
||||
/**
|
||||
* A flow that picks its destination mid-flight has nothing to attribute an upload to yet, so it waits for the send.
|
||||
*/
|
||||
private fun shouldPreUpload(metered: Boolean): Boolean {
|
||||
return !metered && args.mode != MediaSendActivityContract.Mode.ChooseAfterMediaSelection
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
Reference in New Issue
Block a user