diff --git a/app.config.js b/app.config.js index 1461b7b885..88a71a08e1 100644 --- a/app.config.js +++ b/app.config.js @@ -50,7 +50,6 @@ module.exports = function (config) { runtimeVersion: { policy: 'appVersion', }, - orientation: 'portrait', icon: './assets/app-icons/ios_icon_default_light.png', userInterfaceStyle: 'automatic', primaryColor: '#1083fe', @@ -89,8 +88,8 @@ module.exports = function (config) { 'ja', 'km', 'ko', + 'ne', 'nl', - 'np', 'pl', 'pt-BR', 'ro', @@ -346,6 +345,7 @@ module.exports = function (config) { }, }, ], + ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}], ].filter(Boolean), extra: { eas: { diff --git a/lingui.config.js b/lingui.config.js index da43e35615..fb81e5d9ec 100644 --- a/lingui.config.js +++ b/lingui.config.js @@ -19,8 +19,8 @@ module.exports = { 'ja', 'km', 'ko', + 'ne', 'nl', - 'np', 'pl', 'pt-BR', 'ro', diff --git a/package.json b/package.json index 5e1394c2cf..8eafdd6a19 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "expo-media-library": "~17.0.3", "expo-navigation-bar": "~4.0.4", "expo-notifications": "~0.29.11", + "expo-screen-orientation": "^8.0.2", "expo-sharing": "^13.0.0", "expo-splash-screen": "~0.29.18", "expo-status-bar": "~2.0.0", diff --git a/src/App.native.tsx b/src/App.native.tsx index 780295ddce..d1d6b7213e 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -10,6 +10,7 @@ import { initialWindowMetrics, SafeAreaProvider, } from 'react-native-safe-area-context' +import * as ScreenOrientation from 'expo-screen-orientation' import * as SplashScreen from 'expo-splash-screen' import * as SystemUI from 'expo-system-ui' import {msg} from '@lingui/macro' @@ -22,7 +23,7 @@ import {s} from '#/lib/styles' import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' -import {isIOS} from '#/platform/detection' +import {isAndroid, isIOS} from '#/platform/detection' import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' @@ -77,6 +78,10 @@ SplashScreen.preventAutoHideAsync() if (isIOS) { SystemUI.setBackgroundColorAsync('black') } +if (isAndroid) { + // iOS is handled by the config plugin -sfn + ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) +} /** * Begin geolocation ASAP diff --git a/src/alf/util/navigationBar.ts b/src/alf/util/navigationBar.ts index face869837..cb315f70a2 100644 --- a/src/alf/util/navigationBar.ts +++ b/src/alf/util/navigationBar.ts @@ -1,4 +1,5 @@ import * as NavigationBar from 'expo-navigation-bar' +import * as SystemUI from 'expo-system-ui' import {isAndroid} from '#/platform/detection' import {Theme} from '../types' @@ -9,10 +10,12 @@ export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) { NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor) NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor) NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark') + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) } else { NavigationBar.setBackgroundColorAsync('black') NavigationBar.setBorderColorAsync('black') NavigationBar.setButtonStyleAsync('light') + SystemUI.setBackgroundColorAsync('black') } } } diff --git a/src/components/hooks/dates.ts b/src/components/hooks/dates.ts index 92b715eafe..3da33a3ae9 100644 --- a/src/components/hooks/dates.ts +++ b/src/components/hooks/dates.ts @@ -63,8 +63,8 @@ const locales: Record = { ja, km, ko, + ne: undefined, nl, - np: undefined, pl, ['pt-BR']: ptBR, ro, diff --git a/src/locale/__tests__/helpers.test.ts b/src/locale/__tests__/helpers.test.ts index f10ac71d79..f4a3d1b5fa 100644 --- a/src/locale/__tests__/helpers.test.ts +++ b/src/locale/__tests__/helpers.test.ts @@ -12,5 +12,5 @@ test('sanitizeAppLanguageSetting', () => { expect(sanitizeAppLanguageSetting('en,foo')).toBe(AppLanguage.en) expect(sanitizeAppLanguageSetting('foo,en')).toBe(AppLanguage.en) expect(sanitizeAppLanguageSetting('vi')).toBe(AppLanguage.vi) - expect(sanitizeAppLanguageSetting('np')).toBe(AppLanguage.np) + expect(sanitizeAppLanguageSetting('ne')).toBe(AppLanguage.ne) }) diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts index 6ba1526e9a..a0d36421ac 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -153,10 +153,10 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage { return AppLanguage.km case 'ko': return AppLanguage.ko + case 'ne': + return AppLanguage.ne case 'nl': return AppLanguage.nl - case 'np': - return AppLanguage.np case 'pl': return AppLanguage.pl case 'pt-BR': diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index 167e36d354..e59a59f6e4 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -29,8 +29,8 @@ import {messages as messagesIt} from '#/locale/locales/it/messages' import {messages as messagesJa} from '#/locale/locales/ja/messages' import {messages as messagesKm} from '#/locale/locales/km/messages' import {messages as messagesKo} from '#/locale/locales/ko/messages' +import {messages as messagesNe} from '#/locale/locales/ne/messages' import {messages as messagesNl} from '#/locale/locales/nl/messages' -import {messages as messagesNp} from '#/locale/locales/np/messages' import {messages as messagesPl} from '#/locale/locales/pl/messages' import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages' import {messages as messagesRo} from '#/locale/locales/ro/messages' @@ -185,6 +185,10 @@ export async function dynamicActivate(locale: AppLanguage) { ]) break } + case AppLanguage.ne: { + i18n.loadAndActivate({locale, messages: messagesNe}) + break + } case AppLanguage.nl: { i18n.loadAndActivate({locale, messages: messagesNl}) await Promise.all([ @@ -193,10 +197,6 @@ export async function dynamicActivate(locale: AppLanguage) { ]) break } - case AppLanguage.np: { - i18n.loadAndActivate({locale, messages: messagesNp}) - break - } case AppLanguage.pl: { i18n.loadAndActivate({locale, messages: messagesPl}) await Promise.all([ diff --git a/src/locale/i18n.web.ts b/src/locale/i18n.web.ts index 83e4acce08..ac32a54728 100644 --- a/src/locale/i18n.web.ts +++ b/src/locale/i18n.web.ts @@ -80,12 +80,12 @@ export async function dynamicActivate(locale: AppLanguage) { mod = await import(`./locales/ko/messages`) break } - case AppLanguage.nl: { - mod = await import(`./locales/nl/messages`) + case AppLanguage.ne: { + mod = await import(`./locales/ne/messages`) break } - case AppLanguage.np: { - mod = await import(`./locales/np/messages`) + case AppLanguage.nl: { + mod = await import(`./locales/nl/messages`) break } case AppLanguage.pl: { diff --git a/src/locale/languages.ts b/src/locale/languages.ts index 9e804b11e1..710ad264e1 100644 --- a/src/locale/languages.ts +++ b/src/locale/languages.ts @@ -23,8 +23,8 @@ export enum AppLanguage { ja = 'ja', km = 'km', ko = 'ko', + ne = 'ne', nl = 'nl', - np = 'np', pl = 'pl', pt_BR = 'pt-BR', ro = 'ro', @@ -62,8 +62,8 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [ {code2: AppLanguage.ja, name: '日本語 – Japanese'}, {code2: AppLanguage.km, name: 'ភាសាខ្មែរ – Khmer'}, {code2: AppLanguage.ko, name: '한국어 – Korean'}, + {code2: AppLanguage.ne, name: 'नेपाली – Nepali'}, {code2: AppLanguage.nl, name: 'Nederlands – Dutch'}, - {code2: AppLanguage.np, name: 'नेपाली – Nepali'}, {code2: AppLanguage.pl, name: 'Polski – Polish'}, {code2: AppLanguage.pt_BR, name: 'Português (BR) – Portuguese (BR)'}, {code2: AppLanguage.ro, name: 'Română – Romanian'}, diff --git a/src/locale/locales/np/messages.po b/src/locale/locales/ne/messages.po similarity index 99% rename from src/locale/locales/np/messages.po rename to src/locale/locales/ne/messages.po index c6317b16b9..1f8eabf368 100644 --- a/src/locale/locales/np/messages.po +++ b/src/locale/locales/ne/messages.po @@ -5,7 +5,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: @lingui/cli\n" -"Language: np\n" +"Language: ne\n" "Project-Id-Version: Nepali localization for bluesky-social-app\n" "Report-Msgid-Bugs-To: Divyaswor Makai Shrestha \n" "PO-Revision-Date: \n" diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 96f78a709e..7018d753a2 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -40,6 +40,7 @@ import { useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context' +import * as ScreenOrientation from 'expo-screen-orientation' import {StatusBar} from 'expo-status-bar' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Trans} from '@lingui/macro' @@ -60,11 +61,12 @@ import ImageItem from './components/ImageItem/ImageItem' type Rect = {x: number; y: number; width: number; height: number} +const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP const PIXEL_RATIO = PixelRatio.get() const EDGES = Platform.OS === 'android' && Platform.Version < 35 ? (['top', 'bottom', 'left', 'right'] satisfies Edge[]) - : (['left', 'right'] satisfies Edge[]) // iOS or Android 15+, so no top/bottom safe area + : ([] satisfies Edge[]) // iOS or Android 15+ bleeds into safe area const SLOW_SPRING: WithSpringConfig = { mass: isIOS ? 1.25 : 0.75, @@ -102,6 +104,9 @@ export default function ImageViewRoot({ 'use no memo' const ref = useAnimatedRef() const [activeLightbox, setActiveLightbox] = useState(nextLightbox) + const [orientation, setOrientation] = useState<'portrait' | 'landscape'>( + 'portrait', + ) const openProgress = useSharedValue(0) if (!activeLightbox && nextLightbox) { @@ -140,6 +145,20 @@ export default function ImageViewRoot({ }, ) + // Delay the unlock until after we've finished the scale up animation. + // It's complicated to do the same for locking it back so we don't attempt that. + useAnimatedReaction( + () => openProgress.get() === 1, + (isOpen, wasOpen) => { + if (isOpen && !wasOpen) { + runOnJS(ScreenOrientation.unlockAsync)() + } else if (!isOpen && wasOpen) { + // default is PORTRAIT_UP - set via config plugin in app.config.js -sfn + runOnJS(ScreenOrientation.lockAsync)(PORTRAIT_UP) + } + }, + ) + const onFlyAway = React.useCallback(() => { 'worklet' openProgress.set(0) @@ -154,11 +173,21 @@ export default function ImageViewRoot({ aria-modal accessibilityViewIsModal aria-hidden={!activeLightbox}> - + { + const layout = e.nativeEvent.layout + setOrientation( + layout.height > layout.width ? 'portrait' : 'landscape', + ) + }}> {activeLightbox && ( void onPressSave: (uri: string) => void onPressShare: (uri: string) => void @@ -221,7 +252,7 @@ function ImageView({ const openProgressValue = openProgress.get() if (openProgressValue < 1) { opacity = Math.sqrt(openProgressValue) - } else if (screenSize) { + } else if (screenSize && orientation === 'portrait') { const dragProgress = Math.min( Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2), 1, diff --git a/yarn.lock b/yarn.lock index df8572a289..27c3b4fad6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10621,6 +10621,11 @@ expo-pwa@0.0.127: commander "2.20.0" update-check "1.5.3" +expo-screen-orientation@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/expo-screen-orientation/-/expo-screen-orientation-8.0.2.tgz#69139a1967557a331188d36b4dd615a0e220c2a0" + integrity sha512-YsY7Oumlv1WsHLQVgl1f+vAiMZfzUPGyVF2xc7jxmHKtM+jMzIflDl2qKLGfoB0S9Pgg6Z8V4+c+A8wlCURw4A== + expo-sharing@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f"