kopia lustrzana https://github.com/ryukoposting/Signal-Android
Prevent sending videos over 30s in length to a story.
rodzic
fa13b464f8
commit
043f06e188
|
@ -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);
|
||||
|
|
|
@ -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<Callback>().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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,7 +371,7 @@ class MultiselectForwardFragment :
|
|||
}
|
||||
|
||||
private fun isSelectedMediaValidForStories(): Boolean {
|
||||
return getMultiShareArgs().all { it.isValidForStories }
|
||||
return requireListener<Callback>().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 {
|
||||
|
|
|
@ -47,7 +47,12 @@ class MultiselectForwardFullScreenDialogFragment : FullScreenDialogFragment(), M
|
|||
|
||||
override fun onSearchInputFocused() = Unit
|
||||
|
||||
override fun canSendMediaToStories(): Boolean {
|
||||
return findListener<Callback>()?.canSendMediaToStories() ?: true
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onFinishForwardAction()
|
||||
fun onFinishForwardAction() = Unit
|
||||
fun canSendMediaToStories(): Boolean = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -57,5 +57,6 @@ public interface CameraFragment {
|
|||
void onCameraCountButtonClicked();
|
||||
@NonNull LiveData<Optional<Media>> getMostRecentMediaItem();
|
||||
@NonNull MediaConstraints getMediaConstraints();
|
||||
int getMaxVideoDuration();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,11 +55,11 @@ class MediaSelectionRepository(context: Context) {
|
|||
val uploadRepository = MediaUploadRepository(this.context)
|
||||
val isMetered: Observable<Boolean> = MeteredConnectivity.isMetered(this.context)
|
||||
|
||||
fun populateAndFilterMedia(media: List<Media>, mediaConstraints: MediaConstraints, maxSelection: Int): Single<MediaValidator.FilterResult> {
|
||||
fun populateAndFilterMedia(media: List<Media>, mediaConstraints: MediaConstraints, maxSelection: Int, isStory: Boolean): Single<MediaValidator.FilterResult> {
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Media>) {
|
||||
val newSelectionList: List<Media> = linkedSetOf<Media>().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<Media> = savedInstanceState.getParcelableArrayList(STATE_SELECTION) ?: emptyList()
|
||||
val focused: Media? = savedInstanceState.getParcelable(STATE_FOCUSED)
|
||||
|
|
|
@ -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<Media>, mediaConstraints: MediaConstraints, maxSelection: Int): FilterResult {
|
||||
val filteredMedia = filterForValidMedia(context, media, mediaConstraints)
|
||||
fun filterMedia(context: Context, media: List<Media>, 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<Media>, mediaConstraints: MediaConstraints): List<Media> {
|
||||
private fun filterForValidMedia(context: Context, media: List<Media>, mediaConstraints: MediaConstraints, isStory: Boolean): List<Media> {
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue