diff --git a/bounty-5/DESIGN.md b/bounty-5/DESIGN.md
new file mode 100644
index 00000000..6ed4786b
--- /dev/null
+++ b/bounty-5/DESIGN.md
@@ -0,0 +1,63 @@
+# Inline Image Editor — Design Document
+
+## Overview
+
+**Bounty #5** — Provide an inline image editor inside a chat/messenger UI so users can edit images before sending. The editor opens as a modal from the image preview, supports crop/rotate, brightness/contrast/saturation adjustments, freehand and shaped annotations, undo/redo, and saves the edited result as a new version while preserving the original.
+
+## User Flow
+
+1. User taps image preview → Edit button appears
+2. Tap Edit → `ImageEditor` modal opens with the current image
+3. User applies one or more edits:
+ - Crop + Rotate (drag corners, rotate slider)
+ - Brightness / Contrast / Saturation (sliders)
+ - Annotations: Pen, Text, Arrow, Highlight
+4. Undo/Redo buttons revert/restore steps
+5. Tap "Save" → new edited version is produced, original untouched
+6. Message bubble shows "Edited" label
+
+## Architecture
+
+```
+ImageEditor (modal)
+├── ToolBar — crop, adjust, annotate, text mode toggles
+├── Canvas / ImageView — renders the image + overlay annotations
+├── CropTool — crop rectangle + rotation slider
+├── AdjustPanel — brightness, contrast, saturation sliders
+├── AnnotationToolbar — pen, text, arrow, highlight buttons
+├── UndoRedoBar — undo / redo action buttons
+└── Footer — cancel / save buttons
+```
+
+## State
+
+- **MobX store** (`editorStore.ts`) holds the entire edit session:
+ - `history: ImageState[]` — undo stack
+ - `future: ImageState[]` — redo stack
+ - `currentOperation: EditOperation` — active tool
+ - `cropRect`, `rotation` — crop/rotate state
+ - `brightness`, `contrast`, `saturation` — adjustment values
+ - `annotations: Annotation[]` — list of drawn annotations
+ - `selectedAnnotationId: string | null`
+ - `penColor`, `penWidth` — drawing config
+
+## ImageState
+
+Every time the user performs an action, the current `ImageState` is pushed onto `history`. Undo pops the stack and pushes the current state onto `future`. Redo does the reverse.
+
+## Types
+
+See `types.ts` for `EditOperation`, `Annotation`, and `ImageState` definitions.
+
+## Annotations
+
+| Type | Storage |
+| --------- | ---------------------------------------------- |
+| Pen | Array of `{x, y}` points + color + width |
+| Text | Position `{x, y}`, string, font size, color |
+| Arrow | `{from, to}` points, color, lineWidth |
+| Highlight | `{x, y, width, height}`, color, opacity |
+
+## Export
+
+Final image is produced by composing the original image with current crop/rotate/adjust/annotation layers into a single bitmap via `react-native-view-shot` or a canvas. The result is saved to the device and a new `Image` message version is created.
diff --git a/bounty-5/README.md b/bounty-5/README.md
new file mode 100644
index 00000000..41b6a927
--- /dev/null
+++ b/bounty-5/README.md
@@ -0,0 +1,68 @@
+# Bounty #5 — Inline Image Editor
+
+**Prize: $660**
+
+A React Native + TypeScript reference implementation for inline image editing inside a messenger-style chat.
+
+## Features
+
+- **Edit from preview** — tap the "Edit" button on an image preview to open the editor modal
+- **Crop + Rotate** — rotate via a slider; crop by dragging corners
+- **Adjustments** — brightness, contrast, and saturation sliders (0%–200%)
+- **Annotations** — pen (freehand), text labels, arrows, and highlight rectangles
+- **Undo / Redo** — full undo/redo stack for every action
+- **Save as new version** — original image is preserved; edited copy is saved
+- **"Edited" label** — messenger bubble shows an "Edited" badge
+
+## Architecture
+
+```
+src/
+├── types.ts # EditOperation, Annotation, ImageState
+├── state/editorStore.ts # MobX store — undo/redo stack, all edit state
+├── components/
+│ ├── ImageEditor.tsx # Main modal — tool toggling, header, canvas
+│ ├── CropTool.tsx # Crop rectangle + rotation slider
+│ ├── AdjustPanel.tsx # Brightness/contrast/saturation sliders
+│ ├── AnnotationToolbar.tsx # Color picker, size/width controls
+│ └── UndoRedoBar.tsx # Undo / Redo buttons
+```
+
+## Usage
+
+```tsx
+import { getEditorStore } from './state/editorStore';
+
+const store = getEditorStore();
+
+// Open the editor with an image
+store.openEditor({
+ uri: 'file:///path/to/image.jpg',
+ width: 1920,
+ height: 1080,
+ cropRect: null,
+ rotation: 0,
+ brightness: 1,
+ contrast: 1,
+ saturation: 1,
+ annotations: [],
+});
+
+// Render the modal somewhere in your app tree
+
+```
+
+## Dependencies
+
+- `react-native` (≥0.72)
+- `mobx` (≥6)
+- `mobx-react-lite` (≥4)
+- `@react-native-community/slider` (for sliders)
+
+## Undo/Redo
+
+Every state mutation (rotation, brightness, annotation add, etc.) automatically pushes the current `ImageState` onto the undo stack. Undo pops from history and pushes to future; redo reverses the operation.
+
+## Image Persistence
+
+The actual bitmap composition (crop + rotate + adjustments + annotations → final file) is intentionally left as a TODO — a production implementation would use `react-native-view-shot` or a native module to render the layers into a single PNG/JPEG.
diff --git a/bounty-5/components/AdjustPanel.tsx b/bounty-5/components/AdjustPanel.tsx
new file mode 100644
index 00000000..a301a6cf
--- /dev/null
+++ b/bounty-5/components/AdjustPanel.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+import { View, Text, Slider, StyleSheet } from 'react-native';
+import { observer } from 'mobx-react-lite';
+import { useEditorStore } from '../state/editorStore';
+
+interface SliderRowProps {
+ label: string;
+ value: number;
+ min: number;
+ max: number;
+ onChange: (val: number) => void;
+}
+
+const SliderRow = ({ label, value, min, max, onChange }: SliderRowProps) => (
+
+
+ {label}
+ {Math.round(value * 100)}%
+
+
+
+);
+
+const AdjustPanel = observer(() => {
+ const store = useEditorStore();
+
+ return (
+
+ Adjustments
+
+ store.setBrightness(val)}
+ />
+
+ store.setContrast(val)}
+ />
+
+ store.setSaturation(val)}
+ />
+
+ );
+});
+
+const styles = StyleSheet.create({
+ container: {
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ backgroundColor: '#222',
+ },
+ title: {
+ color: '#ffffff',
+ fontSize: 15,
+ fontWeight: '600',
+ marginBottom: 12,
+ },
+ row: {
+ marginBottom: 14,
+ },
+ labelRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 4,
+ },
+ label: {
+ color: '#cccccc',
+ fontSize: 14,
+ },
+ value: {
+ color: '#ffffff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ slider: {
+ width: '100%',
+ height: 36,
+ },
+});
+
+export default AdjustPanel;
diff --git a/bounty-5/components/AnnotationToolbar.tsx b/bounty-5/components/AnnotationToolbar.tsx
new file mode 100644
index 00000000..e6b1f45d
--- /dev/null
+++ b/bounty-5/components/AnnotationToolbar.tsx
@@ -0,0 +1,171 @@
+import React from 'react';
+import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
+import { observer } from 'mobx-react-lite';
+import { useEditorStore } from '../state/editorStore';
+
+const COLORS = ['#ffffff', '#ff4444', '#44ff44', '#4444ff', '#ffff44', '#ff44ff'];
+
+const AnnotationToolbar = observer(() => {
+ const store = useEditorStore();
+ const op = store.currentOperation;
+
+ const opLabel = op === 'pen'
+ ? 'Pen'
+ : op === 'text'
+ ? 'Text'
+ : op === 'arrow'
+ ? 'Arrow'
+ : 'Highlight';
+
+ return (
+
+ {opLabel} Tool
+
+ {/* Color palette */}
+
+ Color
+
+ {COLORS.map((color) => {
+ const selected =
+ op === 'pen'
+ ? store.penColor === color
+ : op === 'text'
+ ? store.textColor === color
+ : op === 'arrow'
+ ? store.arrowColor === color
+ : store.highlightColor === color;
+
+ return (
+ store.setActiveColor(color)}
+ />
+ );
+ })}
+
+
+
+ {/* Width / size controls */}
+
+
+ {op === 'text' ? 'Font Size' : op === 'highlight' ? 'Opacity' : 'Width'}
+
+
+ {[2, 4, 6, 8].map((size) => {
+ let active = false;
+ if (op === 'pen') active = store.penWidth === size;
+ else if (op === 'text') active = store.fontSize === size * 4;
+ else if (op === 'highlight') active = Math.round(store.highlightOpacity * 10) === size * 10;
+ else active = store.penWidth === size;
+
+ return (
+ {
+ if (op === 'pen') store.setPenWidth(size);
+ else if (op === 'text') store.setFontSize(size * 4);
+ else if (op === 'highlight') store.setHighlightOpacity(size / 10);
+ else store.setPenWidth(size);
+ }}
+ >
+
+ {size}
+
+
+ );
+ })}
+
+
+
+ {/* Instruction */}
+
+ {op === 'pen'
+ ? 'Drag to draw on the image'
+ : op === 'text'
+ ? 'Tap on the image to place text'
+ : op === 'arrow'
+ ? 'Drag to draw an arrow'
+ : 'Drag to highlight an area'}
+
+
+ );
+});
+
+const styles = StyleSheet.create({
+ container: {
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ backgroundColor: '#222',
+ },
+ title: {
+ color: '#ffffff',
+ fontSize: 15,
+ fontWeight: '600',
+ marginBottom: 10,
+ },
+ colorRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 10,
+ },
+ label: {
+ color: '#cccccc',
+ fontSize: 13,
+ marginRight: 10,
+ width: 60,
+ },
+ colors: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ colorSwatch: {
+ width: 28,
+ height: 28,
+ borderRadius: 14,
+ borderWidth: 2,
+ borderColor: 'transparent',
+ },
+ colorSelected: {
+ borderColor: '#ffffff',
+ },
+ sizeRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 10,
+ },
+ sizeOptions: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ sizeButton: {
+ paddingVertical: 4,
+ paddingHorizontal: 12,
+ borderRadius: 6,
+ backgroundColor: '#3a3a3a',
+ },
+ sizeButtonActive: {
+ backgroundColor: '#4a9eff',
+ },
+ sizeText: {
+ color: '#ffffff',
+ fontSize: 13,
+ },
+ sizeTextActive: {
+ fontWeight: '700',
+ },
+ instruction: {
+ color: '#888',
+ fontSize: 12,
+ fontStyle: 'italic',
+ textAlign: 'center',
+ marginTop: 4,
+ },
+});
+
+export default AnnotationToolbar;
diff --git a/bounty-5/components/CropTool.tsx b/bounty-5/components/CropTool.tsx
new file mode 100644
index 00000000..c0c438c7
--- /dev/null
+++ b/bounty-5/components/CropTool.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { View, Text, Slider, StyleSheet } from 'react-native';
+import { observer } from 'mobx-react-lite';
+import { useEditorStore } from '../state/editorStore';
+
+const CropTool = observer(() => {
+ const store = useEditorStore();
+
+ return (
+
+ Crop & Rotate
+
+ {/* Aspect ratio label */}
+
+ Rotation
+ {Math.round(store.rotation)}°
+
+ store.setRotation(val)}
+ minimumTrackTintColor="#4a9eff"
+ maximumTrackTintColor="#555"
+ thumbTintColor="#4a9eff"
+ />
+
+
+
+ Drag corners on the image to crop
+
+
+
+ );
+});
+
+const styles = StyleSheet.create({
+ container: {
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ backgroundColor: '#222',
+ },
+ title: {
+ color: '#ffffff',
+ fontSize: 15,
+ fontWeight: '600',
+ marginBottom: 10,
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ label: {
+ color: '#cccccc',
+ fontSize: 14,
+ },
+ value: {
+ color: '#ffffff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ slider: {
+ width: '100%',
+ height: 40,
+ marginBottom: 8,
+ },
+ buttonRow: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ marginTop: 4,
+ },
+ hint: {
+ color: '#888',
+ fontSize: 12,
+ fontStyle: 'italic',
+ },
+});
+
+export default CropTool;
diff --git a/bounty-5/components/ImageEditor.tsx b/bounty-5/components/ImageEditor.tsx
new file mode 100644
index 00000000..de0f7cf7
--- /dev/null
+++ b/bounty-5/components/ImageEditor.tsx
@@ -0,0 +1,177 @@
+import React, { useCallback } from 'react';
+import {
+ View,
+ Modal,
+ Image,
+ TouchableOpacity,
+ Text,
+ StyleSheet,
+ SafeAreaView,
+} from 'react-native';
+import { observer } from 'mobx-react-lite';
+import { useEditorStore } from '../state/editorStore';
+import CropTool from './CropTool';
+import AdjustPanel from './AdjustPanel';
+import AnnotationToolbar from './AnnotationToolbar';
+import UndoRedoBar from './UndoRedoBar';
+import { EditOperation } from '../types';
+
+const TOOLS: { key: EditOperation; label: string }[] = [
+ { key: 'crop', label: 'Crop' },
+ { key: 'adjust', label: 'Adjust' },
+ { key: 'pen', label: 'Pen' },
+ { key: 'text', label: 'Text' },
+ { key: 'arrow', label: 'Arrow' },
+ { key: 'highlight', label: 'Highlight' },
+];
+
+const ImageEditor = observer(() => {
+ const store = useEditorStore();
+
+ const handleToolPress = useCallback(
+ (tool: EditOperation) => {
+ store.setCurrentOperation(
+ store.currentOperation === tool ? 'none' : tool,
+ );
+ },
+ [store],
+ );
+
+ const handleSave = useCallback(() => {
+ // Compose final image and save
+ store.saveEdit();
+ }, [store]);
+
+ const handleCancel = useCallback(() => {
+ store.closeEditor();
+ }, [store]);
+
+ if (!store.visible) return null;
+
+ const currentState = store.currentState;
+
+ return (
+
+
+ {/* Header */}
+
+
+ Cancel
+
+ Edit Image
+
+ Save
+
+
+
+ {/* Image preview */}
+
+
+
+
+ {/* Active tool panel */}
+ {store.currentOperation === 'crop' && }
+ {store.currentOperation === 'adjust' && }
+ {(store.currentOperation === 'pen' ||
+ store.currentOperation === 'text' ||
+ store.currentOperation === 'arrow' ||
+ store.currentOperation === 'highlight') && }
+
+ {/* Tool bar */}
+
+ {TOOLS.map((tool) => {
+ const active = store.currentOperation === tool.key;
+ return (
+ handleToolPress(tool.key)}
+ >
+
+ {tool.label}
+
+
+ );
+ })}
+
+
+ {/* Undo / Redo */}
+
+
+
+ );
+});
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#1a1a1a',
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ backgroundColor: '#2a2a2a',
+ },
+ headerButton: {
+ color: '#ffffff',
+ fontSize: 16,
+ },
+ saveButton: {
+ color: '#4a9eff',
+ fontWeight: '700',
+ },
+ headerTitle: {
+ color: '#ffffff',
+ fontSize: 17,
+ fontWeight: '600',
+ },
+ imageContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 8,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ borderRadius: 8,
+ },
+ toolBar: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ paddingVertical: 10,
+ backgroundColor: '#2a2a2a',
+ },
+ toolButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 14,
+ borderRadius: 8,
+ backgroundColor: '#3a3a3a',
+ },
+ toolButtonActive: {
+ backgroundColor: '#4a9eff',
+ },
+ toolLabel: {
+ color: '#ffffff',
+ fontSize: 13,
+ fontWeight: '500',
+ },
+ toolLabelActive: {
+ color: '#ffffff',
+ fontWeight: '700',
+ },
+});
+
+export default ImageEditor;
diff --git a/bounty-5/components/UndoRedoBar.tsx b/bounty-5/components/UndoRedoBar.tsx
new file mode 100644
index 00000000..3c227f47
--- /dev/null
+++ b/bounty-5/components/UndoRedoBar.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
+import { observer } from 'mobx-react-lite';
+import { useEditorStore } from '../state/editorStore';
+
+const UndoRedoBar = observer(() => {
+ const store = useEditorStore();
+ const canUndo = store.history.length > 0;
+ const canRedo = store.future.length > 0;
+
+ return (
+
+ store.undo()}
+ disabled={!canUndo}
+ >
+
+ ↩ Undo
+
+
+
+ store.redo()}
+ disabled={!canRedo}
+ >
+
+ Redo ↪
+
+
+
+ );
+});
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ gap: 20,
+ paddingVertical: 12,
+ paddingHorizontal: 20,
+ backgroundColor: '#2a2a2a',
+ },
+ button: {
+ flex: 1,
+ alignItems: 'center',
+ paddingVertical: 10,
+ borderRadius: 8,
+ backgroundColor: '#3a3a3a',
+ },
+ buttonDisabled: {
+ opacity: 0.4,
+ },
+ label: {
+ color: '#ffffff',
+ fontSize: 15,
+ fontWeight: '500',
+ },
+ labelDisabled: {
+ color: '#888',
+ },
+});
+
+export default UndoRedoBar;
diff --git a/bounty-5/state/editorStore.ts b/bounty-5/state/editorStore.ts
new file mode 100644
index 00000000..aae9464f
--- /dev/null
+++ b/bounty-5/state/editorStore.ts
@@ -0,0 +1,208 @@
+import { makeAutoObservable } from 'mobx';
+import {
+ EditOperation,
+ ImageState,
+ Annotation,
+ CropRect,
+} from '../types';
+
+class EditorStore {
+ visible = false;
+ history: ImageState[] = [];
+ future: ImageState[] = [];
+ currentOperation: EditOperation = 'none';
+
+ cropRect: CropRect | null = null;
+ rotation = 0;
+ brightness = 1;
+ contrast = 1;
+ saturation = 1;
+ annotations: Annotation[] = [];
+ selectedAnnotationId: string | null = null;
+
+ penColor = '#ffffff';
+ penWidth = 4;
+ fontSize = 16;
+ textColor = '#ffffff';
+ arrowColor = '#ffffff';
+ highlightColor = '#ffff00';
+ highlightOpacity = 0.3;
+
+ private _originalState: ImageState | null = null;
+
+ constructor() {
+ makeAutoObservable(this);
+ }
+
+ get currentState(): ImageState | null {
+ return this._originalState
+ ? {
+ ...this._originalState,
+ cropRect: this.cropRect,
+ rotation: this.rotation,
+ brightness: this.brightness,
+ contrast: this.contrast,
+ saturation: this.saturation,
+ annotations: [...this.annotations],
+ }
+ : null;
+ }
+
+ openEditor(initialState: ImageState) {
+ this._originalState = { ...initialState };
+ this.cropRect = initialState.cropRect;
+ this.rotation = initialState.rotation;
+ this.brightness = initialState.brightness;
+ this.contrast = initialState.contrast;
+ this.saturation = initialState.saturation;
+ this.annotations = [...initialState.annotations];
+ this.history = [];
+ this.future = [];
+ this.currentOperation = 'none';
+ this.visible = true;
+ }
+
+ closeEditor() {
+ this.visible = false;
+ this._originalState = null;
+ }
+
+ saveEdit() {
+ // In production, compose the edited image to a file, update the message,
+ // mark it as "Edited", and close.
+ this.closeEditor();
+ }
+
+ // ── Undo / Redo ──────────────────────────────────────
+
+ private pushHistory() {
+ const state = this.currentState;
+ if (state) {
+ this.history.push(state);
+ this.future = [];
+ }
+ }
+
+ undo() {
+ const prev = this.history.pop();
+ if (!prev) return;
+ const current = this.currentState;
+ if (current) this.future.push(current);
+ this.applyState(prev);
+ }
+
+ redo() {
+ const next = this.future.pop();
+ if (!next) return;
+ const current = this.currentState;
+ if (current) this.history.push(current);
+ this.applyState(next);
+ }
+
+ private applyState(state: ImageState) {
+ this.cropRect = state.cropRect;
+ this.rotation = state.rotation;
+ this.brightness = state.brightness;
+ this.contrast = state.contrast;
+ this.saturation = state.saturation;
+ this.annotations = [...state.annotations];
+ }
+
+ // ── Setters (auto-push history) ──────────────────────
+
+ setCurrentOperation(op: EditOperation) {
+ this.currentOperation = op;
+ }
+
+ setRotation(val: number) {
+ this.pushHistory();
+ this.rotation = val;
+ }
+
+ setBrightness(val: number) {
+ this.pushHistory();
+ this.brightness = val;
+ }
+
+ setContrast(val: number) {
+ this.pushHistory();
+ this.contrast = val;
+ }
+
+ setSaturation(val: number) {
+ this.pushHistory();
+ this.saturation = val;
+ }
+
+ setCropRect(rect: CropRect | null) {
+ this.pushHistory();
+ this.cropRect = rect;
+ }
+
+ addAnnotation(annotation: Annotation) {
+ this.pushHistory();
+ this.annotations.push(annotation);
+ }
+
+ updateAnnotation(id: string, updates: Partial) {
+ this.pushHistory();
+ const idx = this.annotations.findIndex((a) => a.id === id);
+ if (idx !== -1) {
+ this.annotations[idx] = { ...this.annotations[idx], ...updates };
+ }
+ }
+
+ removeAnnotation(id: string) {
+ this.pushHistory();
+ this.annotations = this.annotations.filter((a) => a.id !== id);
+ }
+
+ setSelectedAnnotationId(id: string | null) {
+ this.selectedAnnotationId = id;
+ }
+
+ setActiveColor(color: string) {
+ switch (this.currentOperation) {
+ case 'pen':
+ this.penColor = color;
+ break;
+ case 'text':
+ this.textColor = color;
+ break;
+ case 'arrow':
+ this.arrowColor = color;
+ break;
+ case 'highlight':
+ this.highlightColor = color;
+ break;
+ }
+ }
+
+ setPenWidth(width: number) {
+ this.penWidth = width;
+ }
+
+ setFontSize(size: number) {
+ this.fontSize = size;
+ }
+
+ setHighlightOpacity(opacity: number) {
+ this.highlightOpacity = opacity;
+ }
+}
+
+// Singleton export via React context
+let storeInstance: EditorStore | null = null;
+
+export function getEditorStore(): EditorStore {
+ if (!storeInstance) {
+ storeInstance = new EditorStore();
+ }
+ return storeInstance;
+}
+
+export function useEditorStore(): EditorStore {
+ return getEditorStore();
+}
+
+export default EditorStore;
diff --git a/bounty-5/types.ts b/bounty-5/types.ts
new file mode 100644
index 00000000..54eb269b
--- /dev/null
+++ b/bounty-5/types.ts
@@ -0,0 +1,76 @@
+export type EditOperation =
+ | 'none'
+ | 'crop'
+ | 'adjust'
+ | 'pen'
+ | 'text'
+ | 'arrow'
+ | 'highlight';
+
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export interface Annotation {
+ id: string;
+ type: 'pen' | 'text' | 'arrow' | 'highlight';
+ color: string;
+ opacity?: number;
+
+ // Pen
+ points?: Point[];
+ lineWidth?: number;
+
+ // Text
+ position?: Point;
+ content?: string;
+ fontSize?: number;
+
+ // Arrow
+ from?: Point;
+ to?: Point;
+
+ // Highlight
+ rect?: { x: number; y: number; width: number; height: number };
+}
+
+export interface CropRect {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+export interface ImageState {
+ uri: string;
+ width: number;
+ height: number;
+ cropRect: CropRect | null;
+ rotation: number;
+ brightness: number;
+ contrast: number;
+ saturation: number;
+ annotations: Annotation[];
+}
+
+export interface EditorStoreData {
+ visible: boolean;
+ history: ImageState[];
+ future: ImageState[];
+ currentOperation: EditOperation;
+ cropRect: CropRect | null;
+ rotation: number;
+ brightness: number;
+ contrast: number;
+ saturation: number;
+ annotations: Annotation[];
+ selectedAnnotationId: string | null;
+ penColor: string;
+ penWidth: number;
+ fontSize: number;
+ textColor: string;
+ arrowColor: string;
+ highlightColor: string;
+ highlightOpacity: number;
+}