Files
orca/mobile/app/pair.tsx
T
Kelvin Amoaba 6aa71261b9 feat(mobile): press feedback everywhere, smoother motion, and Reduce Motion for sheets (#36885)
* fix(mobile): stop page bounce and input zoom; URL keyboard for new browser tabs

* feat(mobile): smoother motion, press feedback everywhere, reduced motion for sheets

Working rings and the tool-run pulse run as compositor animations on the
page. Sheets, the right panel, the page stack and the pager cross-fade under
Reduce Motion. The right panel shares the sheet's drag physics. The composer
cross-fades Send/Stop/Save and the mic, jump-to-latest and prompt cards ease
in, banners ease their height, and every Pressable shows a pressed state.
Pairing gets a success beat, and the clean state fades in after a commit.
2026-11-01 04:29:28 +00:00

96 lines
2.8 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import { ActivityIndicator, Linking, Pressable, Text, View } from 'react-native'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { colors, radii, spacing, typography, pressedStyle } from '../src/theme/mobile-theme'
import { themedStyleSheet } from '../src/theme/themed-styles'
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
import { extractPairingCodeFromUrl } from '../src/transport/pairing'
export default function PairRedirectScreen() {
useMobileColorScheme()
const t = useMobileTranslation()
const router = useRouter()
const params = useLocalSearchParams<{ code?: string }>()
const [missingCode, setMissingCode] = useState(false)
const goHome = useCallback(() => {
router.replace('/')
}, [router])
useEffect(() => {
let disposed = false
async function redirectToConfirm() {
const codeParam = Array.isArray(params.code) ? params.code[0] : params.code
if (codeParam) {
router.replace({ pathname: '/pair-confirm', params: { code: codeParam } })
return
}
const initialUrl = await Linking.getInitialURL().catch(() => null)
const code = initialUrl ? extractPairingCodeFromUrl(initialUrl) : null
if (disposed) {
return
}
if (code) {
router.replace({ pathname: '/pair-confirm', params: { code } })
return
}
setMissingCode(true)
}
void redirectToConfirm()
return () => {
disposed = true
}
}, [params.code, router])
return (
<View style={styles.container}>
{missingCode ? (
<>
<Text style={styles.errorText}>{t('app.pair.missingCode', 'Missing pairing code')}</Text>
<Pressable
style={({ pressed }) => [styles.primaryButton, pressed && pressedStyle]}
onPress={goHome}
>
<Text style={styles.primaryButtonText}>{t('app.pair.backToHome', 'Back to home')}</Text>
</Pressable>
</>
) : (
<ActivityIndicator size="large" color={colors.textSecondary} />
)}
</View>
)
}
const styles = themedStyleSheet(() => ({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.bgBase,
padding: spacing.lg
},
errorText: {
color: colors.statusRed,
fontSize: typography.bodySize,
lineHeight: 20,
marginBottom: spacing.xl,
textAlign: 'center'
},
primaryButton: {
alignItems: 'center',
backgroundColor: colors.textPrimary,
borderRadius: radii.control,
paddingHorizontal: spacing.xl,
paddingVertical: spacing.sm + 2
},
primaryButtonText: {
color: colors.bgBase,
fontSize: typography.bodySize,
fontWeight: '600'
}
}))