import type { NativeStackScreenProps } from '@react-navigation/native-stack' import React, { useCallback, useMemo } from 'react' import IonIcon from 'react-native-vector-icons/Ionicons' import { StyleSheet, View, Text, ListRenderItemInfo, SectionList, SectionListData } from 'react-native' import { CameraDevice, useCameraDevices } from 'react-native-vision-camera' import { CONTENT_SPACING, SAFE_AREA_PADDING } from './Constants' import type { Routes } from './Routes' import { PressableOpacity } from 'react-native-pressable-opacity' import { usePreferredCameraDevice } from './hooks/usePreferredCameraDevice' const keyExtractor = (item: CameraDevice): string => item.id interface SectionType { position: CameraDevice['position'] | 'preferred' } type SectionData = SectionListData interface DeviceProps { device: CameraDevice onPress: () => void } function Device({ device, onPress }: DeviceProps): React.ReactElement { const maxPhotoRes = useMemo( () => device.formats.reduce((prev, curr) => { if (curr.photoWidth * curr.photoHeight > prev.photoWidth * prev.photoHeight) return curr return prev }), [device.formats], ) const maxVideoRes = useMemo( () => device.formats.reduce((prev, curr) => { if (curr.videoWidth * curr.videoHeight > prev.videoWidth * prev.videoHeight) return curr return prev }), [device.formats], ) const deviceTypes = useMemo(() => device.physicalDevices.map((t) => t.replace('-camera', '')).join(' + '), [device.physicalDevices]) return ( {device.name} ({device.position}) {deviceTypes} {maxPhotoRes.photoWidth}x{maxPhotoRes.photoHeight} {maxVideoRes.videoWidth}x{maxVideoRes.videoHeight} @ {maxVideoRes.maxFps} FPS {device.id} ) } type Props = NativeStackScreenProps export function DevicesPage({ navigation }: Props): React.ReactElement { const devices = useCameraDevices() const [preferredDevice, setPreferredDevice] = usePreferredCameraDevice() const sections = useMemo((): SectionData[] => { return [ { position: 'preferred', data: preferredDevice != null ? [preferredDevice] : [], }, { position: 'back', data: devices.filter((d) => d.position === 'back'), }, { position: 'front', data: devices.filter((d) => d.position === 'front'), }, { position: 'external', data: devices.filter((d) => d.position === 'external'), }, ] }, [devices, preferredDevice]) const onDevicePressed = useCallback( (device: CameraDevice) => { setPreferredDevice(device) navigation.navigate('CameraPage') }, [navigation, setPreferredDevice], ) const renderItem = useCallback( ({ item }: ListRenderItemInfo) => { return onDevicePressed(item)} /> }, [onDevicePressed], ) const renderSectionHeader = useCallback(({ section }: { section: SectionData }) => { if (section.data.length === 0) return null return ( {section.position.toUpperCase()} ) }, []) return ( Camera Devices These are all detected Camera devices on your phone. This list will automatically update as you plug devices in or out. ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'white', }, headerContainer: { paddingTop: SAFE_AREA_PADDING.paddingTop, paddingLeft: SAFE_AREA_PADDING.paddingLeft, paddingRight: SAFE_AREA_PADDING.paddingRight, }, header: { fontSize: 38, fontWeight: 'bold', maxWidth: '80%', }, subHeader: { marginTop: 10, fontSize: 18, maxWidth: '80%', }, list: { marginTop: CONTENT_SPACING, }, listContent: { paddingBottom: SAFE_AREA_PADDING.paddingBottom, }, sectionHeader: { paddingHorizontal: CONTENT_SPACING / 2, paddingVertical: 5, }, sectionHeaderText: { opacity: 0.4, fontSize: 16, }, itemContainer: { paddingHorizontal: CONTENT_SPACING, paddingVertical: 7, }, deviceName: { fontSize: 17, marginLeft: 5, flexShrink: 1, fontWeight: 'bold', }, devicePosition: { opacity: 0.4, }, deviceId: { fontSize: 12, opacity: 0.4, }, deviceTypes: { fontSize: 12, opacity: 0.4, }, horizontal: { flexDirection: 'row', alignItems: 'center', }, backButton: { width: 40, height: 40, marginTop: 7, }, resolutionText: { marginLeft: 5, fontSize: 12, }, })