about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/composer/Composer.tsx15
-rw-r--r--src/view/com/composer/videos/SubtitleFilePicker.tsx10
-rw-r--r--src/view/com/profile/FollowButton.tsx11
-rw-r--r--src/view/com/util/images/AutoSizedImage.tsx69
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx4
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx2
-rw-r--r--src/view/screens/Profile.tsx11
7 files changed, 87 insertions, 35 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 4c7892bc0..dfdfb3ebd 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -59,7 +59,7 @@ import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
 import {usePalette} from '#/lib/hooks/usePalette'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {LikelyType} from '#/lib/link-meta/link-meta'
-import {logEvent, useGate} from '#/lib/statsig/statsig'
+import {logEvent} from '#/lib/statsig/statsig'
 import {cleanError} from '#/lib/strings/errors'
 import {insertMentionAt} from '#/lib/strings/mention-manip'
 import {shortenLinks} from '#/lib/strings/rich-text-manip'
@@ -140,7 +140,6 @@ export const ComposePost = observer(function ComposePost({
 }: Props & {
   cancelRef?: React.RefObject<CancelRef>
 }) {
-  const gate = useGate()
   const {currentAccount} = useSession()
   const agent = useAgent()
   const {data: currentProfile} = useProfileQuery({did: currentAccount!.did})
@@ -803,13 +802,11 @@ export const ComposePost = observer(function ComposePost({
           ) : (
             <ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
               <SelectPhotoBtn gallery={gallery} disabled={!canSelectImages} />
-              {gate('video_upload') && (
-                <SelectVideoBtn
-                  onSelectVideo={selectVideo}
-                  disabled={!canSelectImages}
-                  setError={setError}
-                />
-              )}
+              <SelectVideoBtn
+                onSelectVideo={selectVideo}
+                disabled={!canSelectImages}
+                setError={setError}
+              />
               <OpenCameraBtn gallery={gallery} disabled={!canSelectImages} />
               <SelectGifBtn
                 onClose={focusTextInput}
diff --git a/src/view/com/composer/videos/SubtitleFilePicker.tsx b/src/view/com/composer/videos/SubtitleFilePicker.tsx
index 9e0fe0aee..beb3f07a8 100644
--- a/src/view/com/composer/videos/SubtitleFilePicker.tsx
+++ b/src/view/com/composer/videos/SubtitleFilePicker.tsx
@@ -3,6 +3,7 @@ import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {logger} from '#/logger'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -25,9 +26,16 @@ export function SubtitleFilePicker({
   const handlePick = (evt: React.ChangeEvent<HTMLInputElement>) => {
     const selectedFile = evt.target.files?.[0]
     if (selectedFile) {
-      if (selectedFile.type === 'text/vtt') {
+      if (
+        selectedFile.type === 'text/vtt' ||
+        (selectedFile.type === 'text/plain' &&
+          selectedFile.name.endsWith('.vtt'))
+      ) {
         onSelectFile(selectedFile)
       } else {
+        logger.error('Invalid subtitle file type', {
+          safeMessage: `File: ${selectedFile.name} (${selectedFile.type})`,
+        })
         Toast.show(_(msg`Only WebVTT (.vtt) files are supported`))
       }
     }
diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx
index 42adea3cf..aaa5d3454 100644
--- a/src/view/com/profile/FollowButton.tsx
+++ b/src/view/com/profile/FollowButton.tsx
@@ -61,7 +61,7 @@ export function FollowButton({
         label={_(msg({message: 'Unfollow', context: 'action'}))}
       />
     )
-  } else {
+  } else if (!profile.viewer.followedBy) {
     return (
       <Button
         type={unfollowedType}
@@ -70,5 +70,14 @@ export function FollowButton({
         label={_(msg({message: 'Follow', context: 'action'}))}
       />
     )
+  } else {
+    return (
+      <Button
+        type={unfollowedType}
+        labelStyle={labelStyle}
+        onPress={onPressFollow}
+        label={_(msg({message: 'Follow Back', context: 'action'}))}
+      />
+    )
   }
 }
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index f57ab4e3c..932a18280 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -10,7 +10,7 @@ import {Dimensions} from '#/lib/media/types'
 import {isNative} from '#/platform/detection'
 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
-import {Crop_Stroke2_Corner0_Rounded as Crop} from '#/components/icons/Crop'
+import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
 import {Text} from '#/components/Typography'
 
 export function useImageAspectRatio({
@@ -23,9 +23,10 @@ export function useImageAspectRatio({
   const [raw, setAspectRatio] = React.useState<number>(
     dimensions ? calc(dimensions) : 1,
   )
+  // this basically controls the width of the image
   const {isCropped, constrained, max} = React.useMemo(() => {
-    const a34 = 0.75 // max of 3:4 ratio in feeds
-    const constrained = Math.max(raw, a34)
+    const ratio = 1 / 2 // max of 1:2 ratio in feeds
+    const constrained = Math.max(raw, ratio)
     const max = Math.max(raw, 0.25) // max of 1:4 in thread
     const isCropped = raw < constrained
     return {
@@ -68,14 +69,14 @@ export function ConstrainedImage({
   const t = useTheme()
   const {gtMobile} = useBreakpoints()
   /**
-   * Computed as a % value to apply as `paddingTop`
+   * Computed as a % value to apply as `paddingTop`, this basically controls
+   * the height of the image.
    */
   const outerAspectRatio = React.useMemo<DimensionValue>(() => {
-    // capped to square or shorter
     const ratio =
       isNative || !gtMobile
-        ? Math.min(1 / aspectRatio, 1.5)
-        : Math.min(1 / aspectRatio, 1)
+        ? Math.min(1 / aspectRatio, 16 / 9) // 9:16 bounding box
+        : Math.min(1 / aspectRatio, 1) // 1:1 bounding box
     return `${ratio * 100}%`
   }, [aspectRatio, gtMobile])
 
@@ -146,33 +147,59 @@ export function AutoSizedImage({
           style={[
             a.absolute,
             a.flex_row,
-            a.align_center,
-            a.rounded_xs,
-            t.atoms.bg_contrast_25,
             {
-              gap: 3,
-              padding: 3,
               bottom: a.p_xs.padding,
               right: a.p_xs.padding,
-              opacity: 0.8,
+              gap: 3,
             },
             largeAlt && [
               {
                 gap: 4,
-                padding: 5,
               },
             ],
           ]}>
           {isCropped && (
-            <Crop
-              fill={t.atoms.text_contrast_high.color}
-              width={largeAlt ? 18 : 12}
-            />
+            <View
+              style={[
+                a.rounded_xs,
+                t.atoms.bg_contrast_25,
+                {
+                  padding: 3,
+                  opacity: 0.8,
+                },
+                largeAlt && [
+                  {
+                    padding: 5,
+                  },
+                ],
+              ]}>
+              <Fullscreen
+                fill={t.atoms.text_contrast_high.color}
+                width={largeAlt ? 18 : 12}
+              />
+            </View>
           )}
           {hasAlt && (
-            <Text style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
-              ALT
-            </Text>
+            <View
+              style={[
+                a.justify_center,
+                a.rounded_xs,
+                t.atoms.bg_contrast_25,
+                {
+                  padding: 3,
+                  opacity: 0.8,
+                },
+                largeAlt && [
+                  {
+                    padding: 5,
+                  },
+                ],
+              ]}>
+              <Text
+                style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
+                ALT
+              </Text>
+            </View>
           )}
         </View>
       ) : null}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
index 6636883f1..be3f90711 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
@@ -37,11 +37,11 @@ export function TimeIndicator({time}: {time: number}) {
       ]}>
       <Text
         style={[
-          {color: t.palette.white, fontSize: 12},
+          {color: t.palette.white, fontSize: 12, fontVariant: ['tabular-nums']},
           a.font_bold,
           {lineHeight: 1.25},
         ]}>
-        {minutes}:{seconds}
+        {`${minutes}:${seconds}`}
       </Text>
     </Animated.View>
   )
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
index bb15db083..791025f70 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
@@ -370,7 +370,7 @@ export function Controls({
             onPress={onPressPlayPause}
           />
           <View style={a.flex_1} />
-          <Text style={{color: t.palette.white}}>
+          <Text style={{color: t.palette.white, fontVariant: ['tabular-nums']}}>
             {formatTime(currentTime)} / {formatTime(duration)}
           </Text>
           {hasSubtitleTrack && (
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 37111c02e..5ef645981 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -41,6 +41,7 @@ import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
 import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
 import {ScreenHider} from '#/components/moderation/ScreenHider'
 import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
+import {navigate} from '#/Navigation'
 import {ExpoScrollForwarderView} from '../../../modules/expo-scroll-forwarder'
 import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
 import {ProfileLists} from '../com/lists/ProfileLists'
@@ -86,6 +87,16 @@ export function ProfileScreen({route}: Props) {
     }
   }, [resolveError, refetchDid, refetchProfile])
 
+  // Apply hard-coded redirects as need
+  React.useEffect(() => {
+    if (resolveError) {
+      if (name === 'lulaoficial.bsky.social') {
+        console.log('Applying redirect to lula.com.br')
+        navigate('Profile', {name: 'lula.com.br'})
+      }
+    }
+  }, [name, resolveError])
+
   // When we open the profile, we want to reset the posts query if we are blocked.
   React.useEffect(() => {
     if (resolvedDid && profile?.viewer?.blockedBy) {