diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index eeb5e80e8..504679307 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1359,6 +1359,11 @@ public class ConversationFragment extends LoggingFragment implements Multiselect public void onDismissForwardSheet() { } + @Override + public boolean canSendMediaToStories() { + return true; + } + public interface ConversationFragmentListener extends VoiceNoteMediaControllerOwner { boolean isKeyboardOpen(); void setThreadId(long threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt index ecf32c579..90b2df466 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt @@ -11,6 +11,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.util.fragments.findListener +import org.thoughtcrime.securesms.util.fragments.requireListener class MultiselectForwardBottomSheet : FixedRoundedCornerBottomSheetDialogFragment(), MultiselectForwardFragment.Callback { @@ -43,6 +44,10 @@ class MultiselectForwardBottomSheet : FixedRoundedCornerBottomSheetDialogFragmen return backgroundColor } + override fun canSendMediaToStories(): Boolean { + return requireListener().canSendMediaToStories() + } + override fun setResult(bundle: Bundle) { setFragmentResult(MultiselectForwardFragment.RESULT_KEY, bundle) } @@ -67,5 +72,6 @@ class MultiselectForwardBottomSheet : FixedRoundedCornerBottomSheetDialogFragmen interface Callback { fun onFinishForwardAction() fun onDismissForwardSheet() + fun canSendMediaToStories(): Boolean = true } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index d0823a3b8..348e36854 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -371,7 +371,7 @@ class MultiselectForwardFragment : } private fun isSelectedMediaValidForStories(): Boolean { - return getMultiShareArgs().all { it.isValidForStories } + return requireListener().canSendMediaToStories() && getMultiShareArgs().all { it.isValidForStories } } private fun isSelectedMediaValidForNonStories(): Boolean { @@ -393,6 +393,7 @@ class MultiselectForwardFragment : fun setResult(bundle: Bundle) fun getContainer(): ViewGroup fun getDialogBackgroundColor(): Int + fun canSendMediaToStories(): Boolean = true } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt index 4d4364e26..be3d31482 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt @@ -47,7 +47,12 @@ class MultiselectForwardFullScreenDialogFragment : FullScreenDialogFragment(), M override fun onSearchInputFocused() = Unit + override fun canSendMediaToStories(): Boolean { + return findListener()?.canSendMediaToStories() ?: true + } + interface Callback { - fun onFinishForwardAction() + fun onFinishForwardAction() = Unit + fun canSendMediaToStories(): Boolean = true } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java index 4cd5b8a5d..a3d1ed6f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java @@ -145,6 +145,11 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera return MediaConstraints.getPushMediaConstraints(); } + @Override + public int getMaxVideoDuration() { + return -1; + } + @Override public void onTouchEventsNeeded(boolean needed) { } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java index f1f1b3ba7..6ba32eb96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java @@ -57,5 +57,6 @@ public interface CameraFragment { void onCameraCountButtonClicked(); @NonNull LiveData> getMostRecentMediaItem(); @NonNull MediaConstraints getMediaConstraints(); + int getMaxVideoDuration(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index 2d4f8845f..69b490d4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -305,6 +305,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { camera.setCaptureMode(SignalCameraView.CaptureMode.MIXED); int maxDuration = VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), controller.getMediaConstraints()); + if (controller.getMaxVideoDuration() > 0) { + maxDuration = controller.getMaxVideoDuration(); + } + Log.d(TAG, "Max duration: " + maxDuration + " sec"); captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper( diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.java index b22a28d7c..1844627c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.java @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.video.VideoBitRateCalculator; import org.thoughtcrime.securesms.video.VideoPlayer; import java.io.IOException; +import java.util.concurrent.TimeUnit; public class VideoEditorFragment extends Fragment implements VideoEditorHud.EventListener, MediaSendPageFragment { @@ -34,6 +35,7 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even private static final String KEY_MAX_OUTPUT = "max_output_size"; private static final String KEY_MAX_SEND = "max_send_size"; private static final String KEY_IS_VIDEO_GIF = "is_video_gif"; + private static final String KEY_MAX_DURATION = "max_duration"; private final Throttler videoScanThrottle = new Throttler(150); private final Handler handler = new Handler(Looper.getMainLooper()); @@ -46,14 +48,16 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even @Nullable private VideoEditorHud hud; private Runnable updatePosition; private boolean isInEdit; - private boolean wasPlayingBeforeEdit; + private boolean wasPlayingBeforeEdit; + private long maxVideoDurationUs; - public static VideoEditorFragment newInstance(@NonNull Uri uri, long maxCompressedVideoSize, long maxAttachmentSize, boolean isVideoGif) { + public static VideoEditorFragment newInstance(@NonNull Uri uri, long maxCompressedVideoSize, long maxAttachmentSize, boolean isVideoGif, long maxVideoDuration) { Bundle args = new Bundle(); args.putParcelable(KEY_URI, uri); args.putLong(KEY_MAX_OUTPUT, maxCompressedVideoSize); args.putLong(KEY_MAX_SEND, maxAttachmentSize); args.putBoolean(KEY_IS_VIDEO_GIF, isVideoGif); + args.putLong(KEY_MAX_DURATION, maxVideoDuration); VideoEditorFragment fragment = new VideoEditorFragment(); fragment.setArguments(args); @@ -84,8 +88,9 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even player = view.findViewById(R.id.video_player); - uri = requireArguments().getParcelable(KEY_URI); - isVideoGif = requireArguments().getBoolean(KEY_IS_VIDEO_GIF); + uri = requireArguments().getParcelable(KEY_URI); + isVideoGif = requireArguments().getBoolean(KEY_IS_VIDEO_GIF); + maxVideoDurationUs = TimeUnit.MILLISECONDS.toMicros(requireArguments().getLong(KEY_MAX_DURATION)); long maxOutput = requireArguments().getLong(KEY_MAX_OUTPUT); long maxSend = requireArguments().getLong(KEY_MAX_SEND); @@ -117,6 +122,7 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even } else if (MediaConstraints.isVideoTranscodeAvailable()) { hud = view.findViewById(R.id.video_editor_hud); hud.setEventListener(this); + clampToMaxVideoDuration(data, true); updateHud(data); if (data.durationEdited) { player.clip(data.startTimeUs, data.endTimeUs, autoplay); @@ -279,12 +285,15 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even boolean wasEdited = data.durationEdited; boolean durationEdited = clampedStartTime > 0 || endTimeUs < totalDurationUs; + boolean endMoved = data.endTimeUs != endTimeUs; data.durationEdited = durationEdited; data.totalDurationUs = totalDurationUs; data.startTimeUs = clampedStartTime; data.endTimeUs = endTimeUs; + clampToMaxVideoDuration(data, !endMoved); + if (editingComplete) { isInEdit = false; videoScanThrottle.clear(); @@ -338,6 +347,26 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even }); } + private void clampToMaxVideoDuration(@NonNull Data data, boolean clampEnd) { + if (!MediaConstraints.isVideoTranscodeAvailable()) { + return; + } + + if ((data.endTimeUs - data.startTimeUs) <= maxVideoDurationUs) { + return; + } + + data.durationEdited = true; + + if (clampEnd) { + data.endTimeUs = data.startTimeUs + maxVideoDurationUs; + } else { + data.startTimeUs = data.endTimeUs - maxVideoDurationUs; + } + + updateHud(data); + } + public static class Data { boolean durationEdited; long totalDurationUs; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index d0203e59d..448dd1a7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.contacts.paged.ContactSearchState +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFullScreenDialogFragment import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment @@ -51,7 +52,8 @@ class MediaSelectionActivity : EmojiKeyboardPageFragment.Callback, EmojiEventListener, EmojiSearchFragment.Callback, - SearchConfigurationProvider { + SearchConfigurationProvider, + MultiselectForwardFullScreenDialogFragment.Callback { private var animateInShadowLayerValueAnimator: ValueAnimator? = null private var animateInTextColorValueAnimator: ValueAnimator? = null @@ -464,4 +466,8 @@ class MediaSelectionActivity : } } } + + override fun canSendMediaToStories(): Boolean { + return viewModel.canShareSelectedMediaToStory() + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt index 904fae876..8ffc2dee8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt @@ -55,11 +55,11 @@ class MediaSelectionRepository(context: Context) { val uploadRepository = MediaUploadRepository(this.context) val isMetered: Observable = MeteredConnectivity.isMetered(this.context) - fun populateAndFilterMedia(media: List, mediaConstraints: MediaConstraints, maxSelection: Int): Single { + fun populateAndFilterMedia(media: List, mediaConstraints: MediaConstraints, maxSelection: Int, isStory: Boolean): Single { return Single.fromCallable { val populatedMedia = mediaRepository.getPopulatedMedia(context, media) - MediaValidator.filterMedia(context, populatedMedia, mediaConstraints, maxSelection) + MediaValidator.filterMedia(context, populatedMedia, mediaConstraints, maxSelection, isStory) }.subscribeOn(Schedulers.io()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index c0af050b0..e4c589d66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.scribbles.ImageEditorFragment +import org.thoughtcrime.securesms.sharing.MultiShareArgs import org.thoughtcrime.securesms.util.SingleLiveEvent import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.livedata.Store @@ -105,6 +106,10 @@ class MediaSelectionViewModel( addMedia(listOf(media)) } + fun isStory(): Boolean { + return store.state.isStory + } + private fun addMedia(media: List) { val newSelectionList: List = linkedSetOf().apply { addAll(store.state.selectedMedia) @@ -113,7 +118,7 @@ class MediaSelectionViewModel( disposables.add( repository - .populateAndFilterMedia(newSelectionList, getMediaConstraints(), store.state.maxSelection) + .populateAndFilterMedia(newSelectionList, getMediaConstraints(), store.state.maxSelection, store.state.isStory) .subscribe { filterResult -> if (filterResult.filteredMedia.isNotEmpty()) { store.update { @@ -340,6 +345,10 @@ class MediaSelectionViewModel( return store.state.selectedMedia.isNotEmpty() } + fun canShareSelectedMediaToStory(): Boolean { + return store.state.selectedMedia.all { MultiShareArgs.isValidStoryDuration(it) } + } + fun onRestoreState(savedInstanceState: Bundle) { val selection: List = savedInstanceState.getParcelableArrayList(STATE_SELECTION) ?: emptyList() val focused: Media? = savedInstanceState.getParcelable(STATE_FOCUSED) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt index bbabd6b56..ebb942d72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt @@ -3,13 +3,14 @@ package org.thoughtcrime.securesms.mediasend.v2 import android.content.Context import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mms.MediaConstraints +import org.thoughtcrime.securesms.sharing.MultiShareArgs import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.Util object MediaValidator { - fun filterMedia(context: Context, media: List, mediaConstraints: MediaConstraints, maxSelection: Int): FilterResult { - val filteredMedia = filterForValidMedia(context, media, mediaConstraints) + fun filterMedia(context: Context, media: List, mediaConstraints: MediaConstraints, maxSelection: Int, isStory: Boolean): FilterResult { + val filteredMedia = filterForValidMedia(context, media, mediaConstraints, isStory) val isAllMediaValid = filteredMedia.size == media.size var error: FilterError? = null @@ -45,12 +46,15 @@ object MediaValidator { return FilterResult(truncatedMedia, error, bucketId) } - private fun filterForValidMedia(context: Context, media: List, mediaConstraints: MediaConstraints): List { + private fun filterForValidMedia(context: Context, media: List, mediaConstraints: MediaConstraints, isStory: Boolean): List { return media .filter { m -> isSupportedMediaType(m.mimeType) } .filter { m -> MediaUtil.isImageAndNotGif(m.mimeType) || isValidGif(context, m, mediaConstraints) || isValidVideo(context, m, mediaConstraints) } + .filter { m -> + MediaConstraints.isVideoTranscodeAvailable() || !isStory || MultiShareArgs.isValidStoryDuration(m) + } } private fun isValidGif(context: Context, media: Media, mediaConstraints: MediaConstraints): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt index dd801475d..be6cda0a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt @@ -20,10 +20,12 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator.Companion import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.navigation.safeNavigate import java.io.FileDescriptor import java.util.Optional +import java.util.concurrent.TimeUnit private val TAG = Log.tag(MediaCaptureFragment::class.java) @@ -165,6 +167,10 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme return sharedViewModel.getMediaConstraints() } + override fun getMaxVideoDuration(): Int { + return if (sharedViewModel.isStory()) TimeUnit.MILLISECONDS.toSeconds(Stories.MAX_VIDEO_DURATION_MILLIS).toInt() else -1 + } + private fun isFirst(): Boolean { return arguments?.getBoolean("first") == true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt index 465eca09c..dc57abca6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt @@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.VideoEditorFragment import org.thoughtcrime.securesms.mediasend.v2.HudCommand import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel +import org.thoughtcrime.securesms.stories.Stories private const val VIDEO_EDITOR_TAG = "video.editor.fragment" @@ -81,7 +82,8 @@ class MediaReviewVideoPageFragment : Fragment(R.layout.fragment_container), Vide requireUri(), requireMaxCompressedVideoSize(), requireMaxAttachmentSize(), - requireIsVideoGif() + requireIsVideoGif(), + requireMaxVideoDuration() ) childFragmentManager.beginTransaction() @@ -100,6 +102,7 @@ class MediaReviewVideoPageFragment : Fragment(R.layout.fragment_container), Vide private fun requireMaxCompressedVideoSize(): Long = sharedViewModel.getMediaConstraints().getCompressedVideoMaxSize(requireContext()).toLong() private fun requireMaxAttachmentSize(): Long = sharedViewModel.getMediaConstraints().getVideoMaxSize(requireContext()).toLong() private fun requireIsVideoGif(): Boolean = requireNotNull(requireArguments().getBoolean(ARG_IS_VIDEO_GIF)) + private fun requireMaxVideoDuration(): Long = if (sharedViewModel.isStory()) Stories.MAX_VIDEO_DURATION_MILLIS else Long.MAX_VALUE companion object { private const val ARG_URI = "arg.uri" diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java index 2347bd959..3d16c3558 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java @@ -12,6 +12,7 @@ import com.annimon.stream.Stream; import org.signal.core.util.BreakIteratorCompat; import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey; +import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mediasend.Media; @@ -26,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public final class MultiShareArgs implements Parcelable { @@ -150,7 +152,10 @@ public final class MultiShareArgs implements Parcelable { public boolean isValidForStories() { return isTextStory || - !media.isEmpty() && media.stream().allMatch(m -> MediaUtil.isImageOrVideoType(m.getMimeType()) && !MediaUtil.isGif(m.getMimeType())) || + !media.isEmpty() && media.stream().allMatch( + m -> MediaUtil.isImageOrVideoType(m.getMimeType()) && + isValidStoryDuration(m) + ) || MediaUtil.isImageType(dataType) || MediaUtil.isVideoType(dataType) || isValidForTextStoryGeneration(); @@ -160,6 +165,25 @@ public final class MultiShareArgs implements Parcelable { return !isTextStory; } + public static boolean isValidStoryDuration(@NonNull Media media) { + if (MediaUtil.isVideoType(media.getMimeType())) { + if (media.getDuration() > 0 && media.getDuration() <= Stories.MAX_VIDEO_DURATION_MILLIS) { + return true; + } else if (media.getTransformProperties().isPresent()) { + AttachmentDatabase.TransformProperties transformProperties = media.getTransformProperties().get(); + if (transformProperties.isVideoTrim()) { + return transformProperties.getVideoTrimEndTimeUs() - transformProperties.getVideoTrimStartTimeUs() <= TimeUnit.MILLISECONDS.toMicros(Stories.MAX_VIDEO_DURATION_MILLIS); + } else { + return false; + } + } else { + return false; + } + } else { + return true; + } + } + public boolean isValidForTextStoryGeneration() { if (isTextStory || !media.isEmpty()) { return false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt index 7c2ce9634..9815e7c67 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt @@ -18,11 +18,15 @@ import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.BottomSheetUtil import org.thoughtcrime.securesms.util.FeatureFlags +import java.util.concurrent.TimeUnit object Stories { const val MAX_BODY_SIZE = 700 + @JvmField + val MAX_VIDEO_DURATION_MILLIS = TimeUnit.SECONDS.toMillis(30) + @JvmStatic fun isFeatureAvailable(): Boolean { return FeatureFlags.stories() && Recipient.self().storiesCapability == Recipient.Capability.SUPPORTED