Notify user when a video is trimmed.

This commit is contained in:
Greyson Parrelli
2026-07-22 20:56:42 -04:00
committed by Michelle Tang
parent cfce0ad8f1
commit 635b5795ea
5 changed files with 50 additions and 14 deletions
@@ -214,6 +214,10 @@ class MediaSelectionActivity :
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleError)
lifecycleDisposable += viewModel.videoTrimmedEvents
.observeOn(AndroidSchedulers.mainThread())
.subscribe { Toast.makeText(this, R.string.MediaReviewFragment__video_trimmed_to_fit, Toast.LENGTH_SHORT).show() }
onBackPressedDispatcher.addCallback(OnBackPressed())
if (savedInstanceState == null && intent.getBooleanExtra(IS_FOR_QUICK_RESTORE, false)) {
@@ -82,6 +82,9 @@ class MediaSelectionViewModel(
val mediaErrors: BehaviorSubject<MediaValidator.FilterError> = BehaviorSubject.createDefault(MediaValidator.FilterError.None)
val hudCommands: Observable<HudCommand> = internalHudCommands
private val _videoTrimmedEvents = PublishSubject.create<Unit>()
val videoTrimmedEvents: Observable<Unit> = _videoTrimmedEvents
private val disposables = CompositeDisposable()
private val isMeteredDisposable: Disposable = repository.isMetered.subscribe { metered ->
@@ -167,19 +170,20 @@ class MediaSelectionViewModel(
.populateAndFilterMedia(newSelectionList, getMediaConstraints(), store.state.maxSelection, store.state.isStory)
.subscribe { filterResult ->
if (filterResult.filteredMedia.isNotEmpty()) {
store.update {
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
val maxDuration = it.calculateMaxVideoDurationUs(video.duration.milliseconds)
if (MediaConstraints.isVideoTranscodeAvailable() && duration >= maxDuration) {
video.uri to VideoTrimData(true, duration, 0, maxDuration)
} else {
video.uri to VideoTrimData(false, duration, 0, duration)
}
val existingState = store.state
val initializedVideoEditorStates = filterResult.filteredMedia.filterNot { media -> existingState.editorStateMap.containsKey(media.uri) }
.filter { media -> MediaUtil.isNonGifVideo(media) }
.associate { video: Media ->
val duration = video.duration.milliseconds.inWholeMicroseconds
val maxDuration = existingState.calculateMaxVideoDurationUs(video.duration.milliseconds)
if (MediaConstraints.isVideoTranscodeAvailable() && duration >= maxDuration) {
video.uri to VideoTrimData(true, duration, 0, maxDuration)
} else {
video.uri to VideoTrimData(false, duration, 0, duration)
}
}
store.update {
val updatedCameraFirstCapture = if (it.cameraFirstCapture != null) {
filterResult.filteredMedia.find { filtered -> filtered.uri == it.cameraFirstCapture.uri }
} else {
@@ -194,6 +198,10 @@ class MediaSelectionViewModel(
)
}
if (initializedVideoEditorStates.any { (_, data) -> data.isDurationEdited }) {
_videoTrimmedEvents.onNext(Unit)
}
selectedMediaSubject.onNext(filterResult.filteredMedia)
val newMedia = filterResult.filteredMedia.toSet().intersect(media).toList()
@@ -335,13 +343,22 @@ class MediaSelectionViewModel(
store.update { it.copy(quality = sentMediaQuality, isPreUploadEnabled = false, transcodingConfigs = TranscodingConfigProvider.getConfigsForMediaQuality(sentMediaQuality)) }
repository.uploadRepository.cancelAllUploads()
var videoTrimmed = false
store.state.selectedMedia.forEach { mediaItem ->
if (MediaUtil.isVideoType(mediaItem.contentType) && MediaConstraints.isVideoTranscodeAvailable()) {
val uri = mediaItem.uri
val data = store.state.getOrCreateVideoTrimData(uri)
onEditVideoDuration(totalDurationUs = data.totalInputDurationUs, startTimeUs = data.startTimeUs, endTimeUs = data.endTimeUs, touchEnabled = true, uri = uri)
val before = store.state.getOrCreateVideoTrimData(uri)
onEditVideoDuration(totalDurationUs = before.totalInputDurationUs, startTimeUs = before.startTimeUs, endTimeUs = before.endTimeUs, touchEnabled = true, uri = uri)
val after = store.state.getOrCreateVideoTrimData(uri)
if (after.getDuration() < before.getDuration()) {
videoTrimmed = true
}
}
}
if (videoTrimmed) {
_videoTrimmedEvents.onNext(Unit)
}
}
fun setMessage(text: CharSequence?) {
+1
View File
@@ -6392,6 +6392,7 @@
<string name="MediaReviewFragment__one_or_more_items_were_too_large">One or more items were too large</string>
<string name="MediaReviewFragment__one_or_more_items_were_invalid">One or more items were invalid</string>
<string name="MediaReviewFragment__too_many_items_selected">Too many items selected</string>
<string name="MediaReviewFragment__video_trimmed_to_fit">Your video was trimmed to fit within the size limit</string>
<!-- Small notification presented to the user when they set their video to view-once mode -->
<string name="MediaReviewFragment__video_set_to_view_once">Video set to view once</string>
<!-- Small notification presented to the user when they set their photo to view-once mode -->
@@ -469,6 +469,10 @@ class MediaSendViewModel(
)
}
if (initializedVideoEditorStates.any { (_, editorState) -> editorState.videoTrimData.isDurationEdited }) {
internalSnackbarEvents.trySend(SnackbarEvent(message = R.string.MediaSendViewModel__video_trimmed_to_fit))
}
// Update story requirements
updateStorySendRequirements(filterResult.filteredMedia)
@@ -607,11 +611,11 @@ class MediaSendViewModel(
preUploadController.cancelAllUploads()
// Re-clamp video durations based on new quality
var videoTrimmed = false
snapshot.selectedMedia.forEach { mediaItem ->
if (isNonGifVideo(mediaItem) && repository.isVideoTranscodeAvailable()) {
val existingData = snapshot.editorStateMap[mediaItem.uri] as? EditorState.VideoTrim
if (existingData != null) {
val maxVideoDurationUs = getMaxVideoDurationUs(existingData.videoTrimData.totalInputDurationUs.microseconds)
onEditVideoDuration(
totalDurationUs = existingData.videoTrimData.totalInputDurationUs,
startTimeUs = existingData.videoTrimData.startTimeUs,
@@ -619,9 +623,17 @@ class MediaSendViewModel(
touchEnabled = true,
uri = mediaItem.uri
)
val updatedData = state.value.editorStateMap[mediaItem.uri] as? EditorState.VideoTrim
if (updatedData != null && updatedData.videoTrimData.getDuration() < existingData.videoTrimData.getDuration()) {
videoTrimmed = true
}
}
}
}
if (videoTrimmed) {
internalSnackbarEvents.trySend(SnackbarEvent(message = R.string.MediaSendViewModel__video_trimmed_to_fit))
}
}
//endregion
@@ -97,6 +97,8 @@
<string name="MediaSendViewModel__error_recording_video">Error recording video</string>
<!-- Displayed when there is an error capturing image -->
<string name="MediaSendViewModel__error_taking_photo">Error taking photo</string>
<!-- Displayed when a video was automatically trimmed so it fits within the size limit -->
<string name="MediaSendViewModel__video_trimmed_to_fit">Your video was trimmed to fit within the size limit</string>
<!-- The title of a dialog notifying that a user was found matching a scanned QR code. The placeholder is a username. Usernames are always latin characters. -->
<string name="UsernameScannedDialog__username_dialog_title">Found %1$s</string>