Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions apps/tlon-mobile/ios/ShareExtension/ShareViewController.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/app/ui/components/Channel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
useRef,
useState,
} from 'react';
import { Platform } from 'react-native';
import { Alert, Platform } from 'react-native';
import {
AnimatePresence,
View,
Expand Down Expand Up @@ -129,7 +129,7 @@ const uploadIntentFromShareIntentFile = (
type: 'fileUri',
localUri,
name: file.fileName || localUri.split('/').pop(),
size: file.size ?? 0,
size: file.size ?? -1,
mimeType: file.mimeType ?? undefined,
voiceMemo: false,
};
Expand Down Expand Up @@ -201,6 +201,7 @@ function usePrefillDraftFromShareIntent({

if (errorMessage) {
shareIntentLogger.log(`Unable to attach shared file: ${errorMessage}`);
Alert.alert('Unable to attach', errorMessage);
}

if (!uploadIntent && !sharedText) return;
Expand Down
18 changes: 14 additions & 4 deletions packages/app/ui/contexts/attachmentRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { expect, test } from 'vitest';

import {
VIDEO_COMPOSITION_ERROR,
VIDEO_VALIDATION_ERROR,
VIDEO_SIZE_LIMIT_ERROR,
VIDEO_SIZE_UNKNOWN_ERROR,
VIDEO_TYPE_ERROR,
canAddAttachment,
inferAllowedVideoMimeType,
} from './attachmentRules';
Expand Down Expand Up @@ -55,7 +57,7 @@ test('rejects video mixed with non-text media', () => {
test('rejects unknown-size local video', () => {
expect(canAddAttachment([], makeVideo({ size: -1 }))).toEqual({
ok: false,
reason: VIDEO_VALIDATION_ERROR,
reason: VIDEO_SIZE_UNKNOWN_ERROR,
kind: 'validation',
});
});
Expand All @@ -72,7 +74,15 @@ test('rejects unknown-size remote video', () => {
)
).toEqual({
ok: false,
reason: VIDEO_VALIDATION_ERROR,
reason: VIDEO_SIZE_UNKNOWN_ERROR,
kind: 'validation',
});
});

test('rejects videos over the size limit', () => {
expect(canAddAttachment([], makeVideo({ size: 151 * 1024 * 1024 }))).toEqual({
ok: false,
reason: VIDEO_SIZE_LIMIT_ERROR,
kind: 'validation',
});
});
Expand All @@ -88,7 +98,7 @@ test('rejects unsupported extension when MIME is missing', () => {
canAddAttachment([], makeVideo({ mimeType: undefined, name: 'clip.bin' }))
).toEqual({
ok: false,
reason: VIDEO_VALIDATION_ERROR,
reason: VIDEO_TYPE_ERROR,
kind: 'validation',
});
});
Expand Down
56 changes: 27 additions & 29 deletions packages/app/ui/contexts/attachmentRules.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Attachment } from '@tloncorp/shared';

const MAX_VIDEO_SIZE_BYTES = 200 * 1024 * 1024;
export const MAX_VIDEO_SIZE_BYTES = 150 * 1024 * 1024;
export const MAX_VIDEO_SIZE_LABEL = '150 MB';
export const VIDEO_COMPOSITION_ERROR =
'Video posts support one video and optional text only.';
export const VIDEO_VALIDATION_ERROR = 'Unsupported video attachment';
export const VIDEO_SIZE_UNKNOWN_ERROR =
'We could not read this video. Try downloading it to your device first.';
export const VIDEO_SIZE_LIMIT_ERROR = `Videos must be under ${MAX_VIDEO_SIZE_LABEL}.`;
export const VIDEO_TYPE_ERROR = 'Video posts support MP4, MOV, and WebM files.';
const VIDEO_EXTENSION_TO_MIME = {
mp4: 'video/mp4',
mov: 'video/quicktime',
Expand Down Expand Up @@ -59,19 +63,22 @@ export function inferAllowedVideoMimeType({
];
}

export function validateVideoSource({
export function getVideoValidationError({
mimeType,
size,
name,
uri,
}: VideoCandidate): boolean {
}: VideoCandidate): string | null {
if (size == null || size < 0) {
return false;
return VIDEO_SIZE_UNKNOWN_ERROR;
}
if (size > MAX_VIDEO_SIZE_BYTES) {
return false;
return VIDEO_SIZE_LIMIT_ERROR;
}
return inferAllowedVideoMimeType({ mimeType, name, uri }) != null;
if (inferAllowedVideoMimeType({ mimeType, name, uri }) == null) {
return VIDEO_TYPE_ERROR;
}
return null;
}

function getVideoName(video: Extract<Attachment, { type: 'video' }>): string {
Expand All @@ -94,33 +101,24 @@ export function canAddAttachment(
nextAttachment: Attachment
): AttachmentValidationResult {
if (nextAttachment.type === 'video') {
const name = getVideoName(nextAttachment);
if (
!validateVideoSource({
mimeType: nextAttachment.mimeType,
size: nextAttachment.size,
name,
uri:
typeof nextAttachment.localFile === 'string'
? nextAttachment.localFile
: undefined,
})
) {
const validationError = getVideoValidationError({
mimeType: nextAttachment.mimeType,
size: nextAttachment.size,
name: getVideoName(nextAttachment),
uri:
typeof nextAttachment.localFile === 'string'
? nextAttachment.localFile
: undefined,
});
if (validationError) {
return {
ok: false,
reason: VIDEO_VALIDATION_ERROR,
reason: validationError,
kind: 'validation',
};
}
}

const hasVideo = prev.some((attachment) => attachment.type === 'video');
const hasOtherNonVideoAttachment = prev.some(
(attachment) => attachment.type !== 'video'
);

if (nextAttachment.type === 'video') {
if (hasOtherNonVideoAttachment) {
if (prev.some((attachment) => attachment.type !== 'video')) {
return {
ok: false,
reason: VIDEO_COMPOSITION_ERROR,
Expand All @@ -130,7 +128,7 @@ export function canAddAttachment(
return { ok: true };
}

if (hasVideo) {
if (prev.some((attachment) => attachment.type === 'video')) {
return {
ok: false,
reason: VIDEO_COMPOSITION_ERROR,
Expand Down
Loading
Loading