about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/constants.ts4
-rw-r--r--src/lib/custom-animations/AccordionAnimation.tsx77
-rw-r--r--src/lib/haptics.ts4
-rw-r--r--src/lib/media/picker.shared.ts6
-rw-r--r--src/lib/media/video/compress.ts13
-rw-r--r--src/lib/statsig/gates.ts2
-rw-r--r--src/lib/strings/helpers.ts4
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