diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/constants.ts | 4 | ||||
-rw-r--r-- | src/lib/custom-animations/AccordionAnimation.tsx | 77 | ||||
-rw-r--r-- | src/lib/haptics.ts | 4 | ||||
-rw-r--r-- | src/lib/media/picker.shared.ts | 6 | ||||
-rw-r--r-- | src/lib/media/video/compress.ts | 13 | ||||
-rw-r--r-- | src/lib/statsig/gates.ts | 2 | ||||
-rw-r--r-- | src/lib/strings/helpers.ts | 4 |
7 files changed, 99 insertions, 11 deletions
diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 21f0ab870..130722b9c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -181,6 +181,10 @@ export const VIDEO_SERVICE = 'https://video.bsky.app' export const VIDEO_SERVICE_DID = 'did:web:video.bsky.app' export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds +/** + * Maximum size of a video in megabytes, _not_ mebibytes. Backend uses + * ISO megabytes. + */ export const VIDEO_MAX_SIZE = 1000 * 1000 * 100 // 100mb export const SUPPORTED_MIME_TYPES = [ diff --git a/src/lib/custom-animations/AccordionAnimation.tsx b/src/lib/custom-animations/AccordionAnimation.tsx new file mode 100644 index 000000000..146735aa6 --- /dev/null +++ b/src/lib/custom-animations/AccordionAnimation.tsx @@ -0,0 +1,77 @@ +import { + type LayoutChangeEvent, + type StyleProp, + View, + type ViewStyle, +} from 'react-native' +import Animated, { + Easing, + FadeInUp, + FadeOutUp, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated' + +import {isIOS, isWeb} from '#/platform/detection' + +type AccordionAnimationProps = React.PropsWithChildren<{ + isExpanded: boolean + duration?: number + style?: StyleProp<ViewStyle> +}> + +function WebAccordion({ + isExpanded, + duration = 300, + style, + children, +}: AccordionAnimationProps) { + const heightValue = useSharedValue(0) + + const animatedStyle = useAnimatedStyle(() => { + const targetHeight = isExpanded ? heightValue.get() : 0 + return { + height: withTiming(targetHeight, { + duration, + easing: Easing.out(Easing.cubic), + }), + overflow: 'hidden', + } + }) + + const onLayout = (e: LayoutChangeEvent) => { + if (heightValue.get() === 0) { + heightValue.set(e.nativeEvent.layout.height) + } + } + + return ( + <Animated.View style={[animatedStyle, style]}> + <View onLayout={onLayout}>{children}</View> + </Animated.View> + ) +} + +function MobileAccordion({ + isExpanded, + duration = 200, + style, + children, +}: AccordionAnimationProps) { + if (!isExpanded) return null + + return ( + <Animated.View + style={style} + entering={FadeInUp.duration(duration)} + exiting={FadeOutUp.duration(duration / 2)} + pointerEvents={isIOS ? 'auto' : 'box-none'}> + {children} + </Animated.View> + ) +} + +export function AccordionAnimation(props: AccordionAnimationProps) { + return isWeb ? <WebAccordion {...props} /> : <MobileAccordion {...props} /> +} diff --git a/src/lib/haptics.ts b/src/lib/haptics.ts index 234be777d..32e371644 100644 --- a/src/lib/haptics.ts +++ b/src/lib/haptics.ts @@ -4,7 +4,6 @@ import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' import {isIOS, isWeb} from '#/platform/detection' import {useHapticsDisabled} from '#/state/preferences/disable-haptics' -import * as Toast from '#/view/com/util/Toast' export function useHaptics() { const isHapticsDisabled = useHapticsDisabled() @@ -23,7 +22,8 @@ export function useHaptics() { // DEV ONLY - show a toast when a haptic is meant to fire on simulator if (__DEV__ && !Device.isDevice) { - Toast.show(`Buzzz!`) + // disabled because it's annoying + // Toast.show(`Buzzz!`) } }, [isHapticsDisabled], diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts index 8fd76f414..8ec1154c8 100644 --- a/src/lib/media/picker.shared.ts +++ b/src/lib/media/picker.shared.ts @@ -17,16 +17,12 @@ export async function openPicker(opts?: ImagePickerOptions) { exif: false, mediaTypes: ['images'], quality: 1, + selectionLimit: 1, ...opts, legacy: true, }) - if (response.assets && response.assets.length > 4) { - Toast.show(t`You may only select up to 4 images`, 'exclamation-circle') - } - return (response.assets ?? []) - .slice(0, 4) .filter(asset => { if (asset.mimeType?.startsWith('image/')) return true Toast.show(t`Only image files are supported`, 'exclamation-circle') diff --git a/src/lib/media/video/compress.ts b/src/lib/media/video/compress.ts index c2d1470c6..1d00bfcea 100644 --- a/src/lib/media/video/compress.ts +++ b/src/lib/media/video/compress.ts @@ -1,8 +1,8 @@ import {getVideoMetaData, Video} from 'react-native-compressor' -import {ImagePickerAsset} from 'expo-image-picker' +import {type ImagePickerAsset} from 'expo-image-picker' -import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants' -import {CompressedVideo} from './types' +import {SUPPORTED_MIME_TYPES, type SupportedMimeTypes} from '#/lib/constants' +import {type CompressedVideo} from './types' import {extToMime} from './util' const MIN_SIZE_FOR_COMPRESSION = 25 // 25mb @@ -20,6 +20,13 @@ export async function compressVideo( file.mimeType as SupportedMimeTypes, ) + if (file.mimeType === 'image/gif') { + // let's hope they're small enough that they don't need compression! + // this compression library doesn't support gifs + // worst case - server rejects them. I think that's fine -sfn + return {uri: file.uri, size: file.fileSize ?? -1, mimeType: 'image/gif'} + } + const minimumFileSizeForCompress = isAcceptableFormat ? MIN_SIZE_FOR_COMPRESSION : 0 diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 66134a462..ef6dc1d4d 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -5,9 +5,9 @@ export type Gate = | 'debug_subscriptions' | 'disable_onboarding_policy_update_notice' | 'explore_show_suggested_feeds' - | 'handle_suggestions' | 'old_postonboarding' | 'onboarding_add_video_feed' + | 'post_follow_profile_suggested_accounts' | 'post_threads_v2_unspecced' | 'remove_show_latest_button' | 'test_gate_1' diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts index ca77c4666..61ad4e85b 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -84,6 +84,10 @@ export function augmentSearchQuery(query: string, {did}: {did?: string}) { return query } + // replace “smart quotes” with normal ones + // iOS keyboard will add fancy unicode quotes, but only normal ones work + query = query.replaceAll(/[“”]/g, '"') + // We don't want to replace substrings that are being "quoted" because those // are exact string matches, so what we'll do here is to split them apart |