npm package: @pigayazilim/react-native-camera
Description: Reusable React Native camera module with photo/video capture, trimming, GIF export and image cropping
Latest version: 1.0.5

# Camera Module

A reusable React Native camera module with photo/video capture, gallery access, video trimming, GIF export, and image cropping. Works on both Android and iOS with zero extra packages — all video processing is done via native platform APIs (ExoPlayer/Media3 on Android, AVFoundation on iOS).

## Features

- Photo & video capture with flash support
- Pinch-to-zoom and double-tap to flip camera
- Multi-select mode for batch photo/video selection
- Gallery access (CameraRoll)
- Video trimming and GIF export (Android: Media3, iOS: AVFoundation + ImageIO)
- Image crop overlay
- Fully themeable via `ThemeProvider`
- Customizable UI labels (i18n friendly)

---

## Installation

### 1. Install the package

```sh
npm install @pigayazilim/react-native-camera
# or
yarn add @pigayazilim/react-native-camera
```

### 2. Install peer dependencies

```sh
npm install \
  @react-native-camera-roll/camera-roll \
  react-native-gesture-handler \
  react-native-reanimated \
  react-native-worklets \
  react-native-safe-area-context \
  react-native-svg \
  react-native-svg-transformer \
  react-native-vision-camera
```

### 3. Verify autolinking

```sh
npx react-native autolinking-status
```

You should see `camera-module` listed and linked for both Android and iOS.

---

## Native Setup

### Android

#### `android/app/src/main/AndroidManifest.xml`

Add the following permissions inside `<manifest>`:

```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<!-- API 33+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- API < 33 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29" />
```

#### `android/app/build.gradle`

Ensure `minSdkVersion` is at least 24:

```groovy
android {
    defaultConfig {
        minSdkVersion 24
    }
}
```

> **Note:** If autolinking doesn't add the module automatically, add this to `android/settings.gradle`:
> ```groovy
> include ':camera-module'
> project(':camera-module').projectDir = new File(rootProject.projectDir, '../modules/camera/android')
> ```

---

### iOS

#### `ios/YourApp/Info.plist`

Add the following permission descriptions:

```xml
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos and videos.</string>

<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to record videos.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to select media.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs permission to save photos to your library.</string>
```

#### Install Pods

```sh
cd ios && bundle exec pod install && cd ..
```

---

## Build Config Setup

### `babel.config.js`

`react-native-reanimated` plugin must be the **last** plugin:

```js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    // ... your other plugins
    'react-native-reanimated/plugin',
  ],
};
```

### `metro.config.js`

Configure `react-native-svg-transformer` for SVG support:

```js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withSVGTransformer } = require('react-native-svg-transformer/react-native');

module.exports = mergeConfig(
  withSVGTransformer(getDefaultConfig(__dirname)),
  {
    // your other metro config
  }
);
```

### `declarations.d.ts` (TypeScript only)

Add SVG module declaration to your project:

```ts
declare module '*.svg' {
  import React from 'react';
  import { SvgProps } from 'react-native-svg';
  const content: React.FC<SvgProps>;
  export default content;
}
```

---

## Wrapping Your App

Your app's root must be wrapped with `GestureHandlerRootView` and `SafeAreaProvider`:

```tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';

export default function Root() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <SafeAreaProvider>
        <App />
      </SafeAreaProvider>
    </GestureHandlerRootView>
  );
}
```

---

## Usage

```tsx
import { CameraModal, usePermissions } from '@pigayazilim/react-native-camera';

export default function MyScreen() {
  const [cameraVisible, setCameraVisible] = useState(false);
  const { hasAllPermissions, checkAndRequestPermissions } = usePermissions();

  const handleOpen = async () => {
    await checkAndRequestPermissions();
    setCameraVisible(true);
  };

  const handleConfirm = (media: Array<{ uri: string; type: string; name: string }>) => {
    console.log('Selected media:', media);
    setCameraVisible(false);
  };

  return (
    <>
      <Button title="Open Camera" onPress={handleOpen} />

      <CameraModal
        visible={cameraVisible}
        onClose={() => setCameraVisible(false)}
        onConfirm={handleConfirm}
        permissionGranted={hasAllPermissions}
        maxVideoDuration={60}
      />
    </>
  );
}
```

---

## API

### `<CameraModal />`

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `visible` | `boolean` | Yes | — | Controls modal visibility |
| `onClose` | `() => void` | Yes | — | Called when modal is dismissed |
| `onConfirm` | `(media: MediaItem[]) => void` | Yes | — | Called with selected media array |
| `permissionGranted` | `boolean` | Yes | — | Whether camera/mic permissions are granted |
| `maxVideoDuration` | `number` | No | `60` | Max video recording duration in seconds |
| `theme` | `Partial<ThemeColors>` | No | — | Override default theme colors |
| `labels` | `Partial<CameraLabels>` | No | — | Override default Turkish UI labels |

#### `MediaItem` (returned in `onConfirm`)

```ts
{
  uri: string;    // File URI
  type: string;   // "image/jpeg" | "image/png" | "video/mp4"
  name: string;   // e.g. "chatPhoto_1234567890.jpeg"
}
```

---

### `usePermissions()`

```ts
const {
  hasAllPermissions,           // boolean — all required permissions granted
  isChecking,                  // boolean — permission check in progress
  hasCameraPermission,         // boolean — camera only
  hasMicPermission,            // boolean — microphone only
  checkAndRequestPermissions,  // () => Promise<boolean> — trigger request
} = usePermissions();
```

---

### `<ThemeProvider />`

Wrap your screen (or the whole app) to apply a custom theme:

```tsx
import { ThemeProvider } from '@pigayazilim/react-native-camera';

<ThemeProvider colors={{ primary: '#FF6B00', gold: '#FFC300' }}>
  <CameraModal ... />
</ThemeProvider>
```

#### `ThemeColors`

```ts
{
  primary: string;
  white: string;
  black: string;
  error: string;
  gold: string;
  overlay: string;
  surface: string;
  border: string;
  shadow: string;
  textSecondary: string;
  redSoft: string;
  redMedium: string;
}
```

---

### `CameraLabels`

Override any UI text (all optional, Turkish defaults):

```ts
type CameraLabels = {
  photoMode?: string;              // default: "FOTOĞRAF"
  videoMode?: string;              // default: "VİDEO"
  permissionDenied?: string;       // default: "Kamera izni verilmedi."
  cameraNotFound?: string;         // default: "Kamera bulunamadı."
  cameraStarting?: string;         // default: "Kamera başlatılıyor..."
  errorTitle?: string;             // default: "Hata"
  micPermissionError?: string;     // default: "Video çekmek için mikrofon izni gerekli."
  recordingError?: string;         // default: "Video kaydı sırasında bir sorun oluştu"
  selectionConflictError?: string; // default: "Öncelikle seçili medyayı onaylamalı veya iptal etmelisiniz."
};
```

Example — English labels:

```tsx
<CameraModal
  ...
  labels={{
    photoMode: 'PHOTO',
    videoMode: 'VIDEO',
    permissionDenied: 'Camera permission denied.',
    cameraNotFound: 'No camera found.',
    cameraStarting: 'Starting camera...',
    errorTitle: 'Error',
    micPermissionError: 'Microphone permission is required to record video.',
    recordingError: 'An error occurred during video recording.',
    selectionConflictError: 'Please confirm or cancel your current selection first.',
  }}
/>
```

---

## Native Video Processing

| Feature | Android | iOS |
|---------|---------|-----|
| Video playback | ExoPlayer (Media3) | AVPlayer |
| Video trim | Media3 Transformer | AVAssetExportSession |
| GIF export | Custom GifEncoder | CGImageDestination (ImageIO) |
| Thumbnails | MediaMetadataRetriever | AVAssetImageGenerator |
| Image crop | BitmapRegionDecoder | CGImage.cropping |

All native code is bundled in the `android/` and `ios/` folders. No external npm packages are required for video processing.