Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/soymatudev/Pokedex-Fleek/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The ScannerScreen component provides the core scanning functionality of the PokéDex Fleek app. It uses the device camera to capture images and analyze dominant colors to identify Pokémon. The component supports both QR/barcode scanning and manual color-based detection. Location: ~/workspace/source/src/features/scanner/ScannerScreen.js

Props

navigation
object
required
React Navigation object for screen navigation

State Management

The component manages three primary state variables:
hasPermission
boolean
default:"false"
Camera permission status from the device
detectedPokemon
object | null
default:"null"
Currently detected Pokémon data from POKEMON_DB
isProcessing
boolean
default:"false"
Indicates whether a scan operation is in progress

Key Functions

hexToHue(hex)

Converts a hexadecimal color value to a hue value (0-360 degrees) for color matching.
hex
string
required
Hex color string (with or without ’#’ prefix)
Returns: number - Hue value in degrees (0-360)
const hexToHue = (hex) => {
    if (!hex || hex.length < 6) return 0;
    const cleanHex = hex.startsWith('#') ? hex : `#${hex}`;

    let r = parseInt(cleanHex.slice(1, 3), 16) / 255;
    let g = parseInt(cleanHex.slice(3, 5), 16) / 255;
    let b = parseInt(cleanHex.slice(5, 7), 16) / 255;

    let max = Math.max(r, g, b), min = Math.min(r, g, b);
    let h, d = max - min;

    if (d === 0) h = 0;
    else if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
    else if (max === g) h = (b - r) / d + 2;
    else if (max === b) h = (r - g) / d + 4;

    return Math.round(h * 60);
};

handleManualScan()

Manually captures a photo and analyzes its dominant color to detect a Pokémon. Process:
  1. Takes a photo using the camera reference
  2. Extracts dominant color using ImageColors.getColors()
  3. Converts color to hue value
  4. Matches hue against POKEMON_DB color ranges
  5. Provides haptic feedback (vibration)
  6. Navigates to Details screen on successful match
const handleManualScan = async () => {
    if (isProcessing) return;
    Vibration.vibrate(70);
    setIsProcessing(true);

    try {
        const photo = await cameraRef.current.takePhoto({ 
            flash: 'off', 
            skipMetadata: true 
        });
        const result = await ImageColors.getColors(photo.path, { 
            fallback: '#000000' 
        });
        const detectedHex = result.platform === 'android' 
            ? result.dominant 
            : result.background;
        const hue = hexToHue(detectedHex);

        const potentialMatches = Object.values(POKEMON_DB).filter(p =>
            hue >= p.colorRange.hueMin && hue <= p.colorRange.hueMax
        );

        if (potentialMatches.length > 0) {
            const randomIndex = Math.floor(Math.random() * potentialMatches.length);
            const match = potentialMatches[randomIndex];
            
            setDetectedPokemon(match);
            Vibration.vibrate([0, 100, 50, 100]);
            
            setTimeout(() => {
                navigation.navigate('Details', { 
                    pokemonId: match.id, 
                    themeColor: match.uiTheme 
                });
                setIsProcessing(false);
                setDetectedPokemon(null);
            }, 500);
        } else {
            alert("No se detectó ADN Pokémon conocido en este objeto.");
            setIsProcessing(false);
        }
    } catch (error) {
        console.error(error);
        Vibration.vibrate(400);
        setIsProcessing(false);
    }
};

codeScanner Configuration

Configures automatic QR code and barcode scanning.
codeTypes
array
default:"['qr', 'ean-13']"
Supported barcode/QR code formats
onCodeScanned
function
Callback fired when a code is detected
const codeScanner = useCodeScanner({
    codeTypes: ['qr', 'ean-13'],
    onCodeScanned: async (codes) => {
        if (isProcessing || detectedPokemon || codes.length === 0) return;
        
        setIsProcessing(true);
        
        try {
            const id = codes[0].value;
            const photo = await cameraRef.current.takePhoto({
                flash: 'off',
                skipMetadata: true
            });
            
            // Color analysis and matching logic
            const result = await ImageColors.getColors(photo.path, { 
                fallback: '#000000' 
            });
            const detectedHex = result.platform === 'android' 
                ? result.dominant 
                : result.background;
            const hue = hexToHue(detectedHex);
            
            const match = Object.values(POKEMON_DB).find(p =>
                hue >= p.colorRange.hueMin && hue <= p.colorRange.hueMax
            );
            
            if (match) {
                setDetectedPokemon(match);
                setTimeout(() => {
                    navigation.navigate('Details', { pokemonId: match.id });
                }, 1000);
            }
        } catch (error) {
            console.error("Error:", error);
            setIsProcessing(false);
        }
    }
});

UI Elements

Reticle (Target Indicator)

Central circular target indicator for aiming the camera:
<View style={styles.reticleContainer}>
    <View style={styles.reticle}>
        <View style={styles.reticleDot} />
    </View>
    <Text style={styles.reticleText}>CENTRAR OBJETIVO</Text>
</View>

Pokéball Scan Button

Stylized button that resembles a Pokéball, triggers manual scanning:
<TouchableOpacity
    style={[
        styles.pokeballButton,
        isProcessing && { borderColor: theme.colors.primary, elevation: 20 }
    ]}
    onPress={handleManualScan}
    disabled={isProcessing}
>
    <View style={[
        styles.pokeballInnerRing,
        isProcessing && { borderColor: '#fff' }
    ]}>
        {isProcessing ? (
            <ActivityIndicator color="#333" />
        ) : (
            <View style={styles.pokeballCenterBtn} />
        )}
    </View>
</TouchableOpacity>

Camera Overlay

Semi-transparent overlay providing visual guidance:
<View style={styles.overlay}>
    {/* Reticle and scan button */}
</View>

Camera Integration

The component uses react-native-vision-camera for camera access:
{isFocused && (
    <Camera
        ref={cameraRef}
        codeScanner={codeScanner}
        device={device}
        isActive={true}
        style={StyleSheet.absoluteFill}
        photo={true}
    />
)}
The camera only renders when the screen is focused (isFocused from useIsFocused() hook)

To Details Screen

On successful Pokémon detection:
navigation.navigate('Details', { 
    pokemonId: match.id, 
    themeColor: match.uiTheme 
});

Back Navigation

Close button in the top-left corner:
<TouchableOpacity 
    style={styles.closeButton} 
    onPress={() => navigation.goBack()}
>
    <Text style={styles.closeIcon}></Text>
</TouchableOpacity>

Permission Handling

Requests camera permission on mount and displays settings prompt if denied:
useEffect(() => {
    (async () => {
        const status = await Camera.requestCameraPermission();
        setHasPermission(status === 'granted');
    })();
}, []);

if (!hasPermission) {
    return (
        <View style={styles.center}>
            <Text style={styles.errorText}>
                Dexter necesita permisos de cámara
            </Text>
            <TouchableOpacity 
                onPress={() => Linking.openSettings()} 
                style={styles.backButton}
            >
                <Text style={{ color: 'white' }}>ABRIR AJUSTES</Text>
            </TouchableOpacity>
        </View>
    );
}

Dependencies

  • react-native-vision-camera - Camera access and frame processing
  • react-native-image-colors - Dominant color extraction
  • react-native-worklets-core - High-performance frame processing
  • @react-navigation/native - Navigation and focus detection

Usage Example

import { ScannerScreen } from './features/scanner/ScannerScreen';

// In your navigation stack
<Stack.Screen 
    name="Scanner" 
    component={ScannerScreen}
    options={{ headerShown: false }}
/>

Build docs developers (and LLMs) love