mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 08:02:31 +00:00
fix(mobile): use neutral camera permission copy (#1504)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "com.stably.orca.mobile",
|
||||
"buildNumber": "10",
|
||||
"buildNumber": "11",
|
||||
"infoPlist": {
|
||||
"NSLocalNetworkUsageDescription": "Orca connects to the desktop app on your local network.",
|
||||
"NSAppTransportSecurity": {
|
||||
|
||||
@@ -567,7 +567,7 @@ export default function HomeScreen() {
|
||||
</Text>
|
||||
<Pressable style={styles.primaryButton} onPress={() => router.push('/pair-scan')}>
|
||||
<QrCode size={17} color={colors.bgBase} />
|
||||
<Text style={styles.primaryButtonText}>Scan Pairing Code</Text>
|
||||
<Text style={styles.primaryButtonText}>Pair Desktop</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState, useRef, useCallback } from 'react'
|
||||
import { View, Text, StyleSheet, Pressable, ActivityIndicator } from 'react-native'
|
||||
import { View, Text, StyleSheet, Pressable, ActivityIndicator, Linking } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { ChevronLeft, Clipboard as ClipboardIcon } from 'lucide-react-native'
|
||||
import { ChevronLeft, Clipboard as ClipboardIcon, QrCode } from 'lucide-react-native'
|
||||
import { decodePairingUrl, parsePairingCode } from '../src/transport/pairing'
|
||||
import { connect } from '../src/transport/rpc-client'
|
||||
import { saveHost, getNextHostName } from '../src/transport/host-store'
|
||||
@@ -146,20 +146,46 @@ export default function PairScanScreen() {
|
||||
}
|
||||
|
||||
if (!permission.granted) {
|
||||
const canAskAgain = permission.canAskAgain !== false
|
||||
return (
|
||||
<View style={[styles.container, containerPadding]}>
|
||||
<Pressable style={styles.backButton} onPress={() => router.back()}>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<View style={styles.centered}>
|
||||
<Text style={styles.title}>Camera Permission</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Orca needs camera access to scan the pairing QR code from your desktop.
|
||||
<Text style={styles.title}>
|
||||
{canAskAgain ? 'Pair with desktop' : 'Camera Access Disabled'}
|
||||
</Text>
|
||||
<Pressable style={styles.primaryButton} onPress={requestPermission}>
|
||||
<Text style={styles.primaryButtonText}>Grant Camera Access</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{canAskAgain
|
||||
? 'Scan the QR code from Orca on your desktop, or paste the pairing code instead.'
|
||||
: 'Enable camera access in Settings, or paste the pairing code instead.'}
|
||||
</Text>
|
||||
<Pressable
|
||||
style={styles.primaryButton}
|
||||
onPress={canAskAgain ? requestPermission : () => void Linking.openSettings()}
|
||||
>
|
||||
{canAskAgain && <QrCode size={16} color={colors.bgBase} />}
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{canAskAgain ? 'Continue' : 'Open Settings'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.pasteButton, pressed && styles.pasteButtonPressed]}
|
||||
onPress={() => setPasteVisible(true)}
|
||||
>
|
||||
<ClipboardIcon size={16} color={colors.textSecondary} />
|
||||
<Text style={styles.pasteButtonText}>Paste code instead</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<TextInputModal
|
||||
visible={pasteVisible}
|
||||
title="Paste pairing code"
|
||||
message="Copy the code shown under the QR on your computer."
|
||||
placeholder="orca://pair#... or paste the code"
|
||||
onSubmit={handlePasteSubmit}
|
||||
onCancel={() => setPasteVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -360,6 +386,7 @@ const styles = StyleSheet.create({
|
||||
marginBottom: spacing.sm
|
||||
},
|
||||
subtitle: {
|
||||
maxWidth: 310,
|
||||
fontSize: typography.bodySize,
|
||||
color: colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
@@ -379,6 +406,10 @@ const styles = StyleSheet.create({
|
||||
lineHeight: 20
|
||||
},
|
||||
primaryButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: spacing.xs,
|
||||
backgroundColor: colors.textPrimary,
|
||||
paddingHorizontal: spacing.xl,
|
||||
paddingVertical: spacing.sm + 2,
|
||||
|
||||
@@ -7,13 +7,15 @@ import { hashOrcaHookScript } from './orca-hook-trust'
|
||||
const hooksCheckMock = vi.fn()
|
||||
const readIssueCommandMock = vi.fn()
|
||||
|
||||
;(globalThis as { window: unknown }).window = {
|
||||
api: {
|
||||
hooks: {
|
||||
check: hooksCheckMock,
|
||||
readIssueCommand: readIssueCommandMock
|
||||
function installHooksApiMock(): void {
|
||||
vi.stubGlobal('window', {
|
||||
api: {
|
||||
hooks: {
|
||||
check: hooksCheckMock,
|
||||
readIssueCommand: readIssueCommandMock
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type PendingPrompt = {
|
||||
@@ -48,6 +50,7 @@ describe('ensureHooksConfirmed', () => {
|
||||
beforeEach(() => {
|
||||
hooksCheckMock.mockReset()
|
||||
readIssueCommandMock.mockReset()
|
||||
installHooksApiMock()
|
||||
__resetTrustPromptChainForTests()
|
||||
})
|
||||
|
||||
@@ -171,17 +174,13 @@ describe('ensureHooksConfirmed', () => {
|
||||
const first = ensureHooksConfirmed(state, 'repo-1', 'setup')
|
||||
const second = ensureHooksConfirmed(state, 'repo-1', 'archive')
|
||||
|
||||
await flush()
|
||||
|
||||
expect(pending).toHaveLength(1)
|
||||
await vi.waitFor(() => expect(pending).toHaveLength(1))
|
||||
expect(pending[0].data.scriptKind).toBe('setup')
|
||||
|
||||
pending[0].resolve('skip')
|
||||
await expect(first).resolves.toBe('skip')
|
||||
|
||||
await flush()
|
||||
|
||||
expect(pending).toHaveLength(2)
|
||||
await vi.waitFor(() => expect(pending).toHaveLength(2))
|
||||
expect(pending[1].data.scriptKind).toBe('archive')
|
||||
|
||||
pending[1].resolve('run')
|
||||
|
||||
Reference in New Issue
Block a user