diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index e0e9340..4c1c6ba 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -35,6 +35,14 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-09-16d', + date: 'September 2026', + items: [ + 'Edge on a folding phone or a tablet: clips are no longer cropped down to the middle. When the screen and the clip are shaped too differently, you get the whole clip with black bars instead, and the caption and buttons sit on those bars. Wide clips gain from this on normal phones too.', + ], + minRuntime: '1.4', + }, { id: '2026-09-16c', date: 'September 2026', diff --git a/mobile/src/components/swipe/ClipCell.tsx b/mobile/src/components/swipe/ClipCell.tsx index 7bc7410..19707ec 100644 --- a/mobile/src/components/swipe/ClipCell.tsx +++ b/mobile/src/components/swipe/ClipCell.tsx @@ -22,7 +22,7 @@ import { Image } from 'expo-image'; import type { VideoPlayer } from 'expo-video'; import { VideoView } from 'expo-video'; import React from 'react'; -import { Animated, Pressable, StyleSheet, Text, View } from 'react-native'; +import { Animated, Pressable, StyleSheet, Text, View, useWindowDimensions } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -49,6 +49,26 @@ function ClipCellBase({ item, active, player, muted, liked, onToggleMute, onLike const [ready, setReady] = React.useState(false); const heart = React.useRef(new Animated.Value(0)).current; const insets = useSafeAreaInsets(); + + // Kadrowac czy dolozyc czarne pasy. + // + // `cover` wypelnia ekran i tnie to, co wystaje. Na zwyklym telefonie pionowy klip traci + // tak ze 20 % szerokosci i nikomu to nie przeszkadza, ale na rozlozonym foldzie ekran + // jest prawie kwadratowy i z pionowego klipu zostaje srodek (zgloszenie Jana + // 2026-09-16). To samo dotyczy klipow poziomych, ktorych na RedGifs jest ok. 19 %. + // Liczymy, ile by sie ucielo: do `MAX_CROP` zostaje `cover`, powyzej `contain`, czyli + // cale wideo i pasy po bokach albo u gory. Podpis i kolumna przyciskow stoja przy + // krawedziach ekranu, wiec przy pasach same wchodza na pasy zamiast na wideo. + // `useWindowDimensions` reaguje na zlozenie i obrot, wiec kadr zmienia sie od razu. + const win = useWindowDimensions(); + const fit = React.useMemo<'cover' | 'contain'>(() => { + const MAX_CROP = 1.3; + const clipAspect = clip.width && clip.height ? clip.width / clip.height : 9 / 16; + const screenAspect = win.width / Math.max(win.height, 1); + if (!clipAspect || !screenAspect) return 'cover'; + const crop = Math.max(screenAspect / clipAspect, clipAspect / screenAspect); + return crop > MAX_CROP ? 'contain' : 'cover'; + }, [clip.width, clip.height, win.width, win.height]); const lift = React.useMemo( () => barHidden.interpolate({ @@ -154,14 +174,14 @@ function ClipCellBase({ item, active, player, muted, liked, onToggleMute, onLike {poster ? ( - + ) : null} {active && player ? ( ) : null}