plant-health-lib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +19 -0
- package/lib/components/index.js.map +1 -0
- package/lib/components/ui.d.ts +33 -0
- package/lib/components/ui.d.ts.map +1 -0
- package/lib/components/ui.js +184 -0
- package/lib/components/ui.js.map +1 -0
- package/lib/contexts/HistoryContext.d.ts +30 -0
- package/lib/contexts/HistoryContext.d.ts.map +1 -0
- package/lib/contexts/HistoryContext.js +76 -0
- package/lib/contexts/HistoryContext.js.map +1 -0
- package/lib/hooks/index.d.ts +3 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +9 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/useFrameClassifier.d.ts +9 -0
- package/lib/hooks/useFrameClassifier.d.ts.map +1 -0
- package/lib/hooks/useFrameClassifier.js +72 -0
- package/lib/hooks/useFrameClassifier.js.map +1 -0
- package/lib/hooks/useHistory.d.ts +3 -0
- package/lib/hooks/useHistory.d.ts.map +1 -0
- package/lib/hooks/useHistory.js +6 -0
- package/lib/hooks/useHistory.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +25 -0
- package/lib/index.js.map +1 -0
- package/lib/navigation/RootNavigator.d.ts +3 -0
- package/lib/navigation/RootNavigator.d.ts.map +1 -0
- package/lib/navigation/RootNavigator.js +36 -0
- package/lib/navigation/RootNavigator.js.map +1 -0
- package/lib/screens/HistoryScreen.d.ts +3 -0
- package/lib/screens/HistoryScreen.d.ts.map +1 -0
- package/lib/screens/HistoryScreen.js +93 -0
- package/lib/screens/HistoryScreen.js.map +1 -0
- package/lib/screens/HomeScreen.d.ts +3 -0
- package/lib/screens/HomeScreen.d.ts.map +1 -0
- package/lib/screens/HomeScreen.js +121 -0
- package/lib/screens/HomeScreen.js.map +1 -0
- package/lib/screens/ResultScreen.d.ts +3 -0
- package/lib/screens/ResultScreen.d.ts.map +1 -0
- package/lib/screens/ResultScreen.js +166 -0
- package/lib/screens/ResultScreen.js.map +1 -0
- package/lib/screens/ScanScreen.d.ts +3 -0
- package/lib/screens/ScanScreen.d.ts.map +1 -0
- package/lib/screens/ScanScreen.js +311 -0
- package/lib/screens/ScanScreen.js.map +1 -0
- package/lib/services/classifier.d.ts +27 -0
- package/lib/services/classifier.d.ts.map +1 -0
- package/lib/services/classifier.js +70 -0
- package/lib/services/classifier.js.map +1 -0
- package/lib/services/diseaseData.d.ts +19 -0
- package/lib/services/diseaseData.d.ts.map +1 -0
- package/lib/services/diseaseData.js +180 -0
- package/lib/services/diseaseData.js.map +1 -0
- package/lib/services/index.d.ts +3 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +20 -0
- package/lib/services/index.js.map +1 -0
- package/lib/theme/index.d.ts +37 -0
- package/lib/theme/index.d.ts.map +1 -0
- package/lib/theme/index.js +52 -0
- package/lib/theme/index.js.map +1 -0
- package/lib/utils/imageToTensor.d.ts +2 -0
- package/lib/utils/imageToTensor.d.ts.map +1 -0
- package/lib/utils/imageToTensor.js +36 -0
- package/lib/utils/imageToTensor.js.map +1 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +19 -0
- package/lib/utils/index.js.map +1 -0
- package/package.json +104 -0
- package/src/assets/README.md +24 -0
- package/src/assets/plant_disease_model.tflite +0 -0
- package/src/components/index.js +9 -0
- package/src/components/index.ts +2 -0
- package/src/components/ui.js +205 -0
- package/src/components/ui.tsx +215 -0
- package/src/contexts/HistoryContext.js +130 -0
- package/src/contexts/HistoryContext.tsx +84 -0
- package/src/hooks/index.js +8 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFrameClassifier.js +120 -0
- package/src/hooks/useFrameClassifier.ts +80 -0
- package/src/hooks/useHistory.js +5 -0
- package/src/hooks/useHistory.ts +2 -0
- package/src/index.js +24 -0
- package/src/index.ts +12 -0
- package/src/navigation/RootNavigator.js +43 -0
- package/src/navigation/RootNavigator.tsx +39 -0
- package/src/screens/HistoryScreen.js +98 -0
- package/src/screens/HistoryScreen.tsx +100 -0
- package/src/screens/HomeScreen.js +125 -0
- package/src/screens/HomeScreen.tsx +137 -0
- package/src/screens/ResultScreen.js +166 -0
- package/src/screens/ResultScreen.tsx +157 -0
- package/src/screens/ScanScreen.js +388 -0
- package/src/screens/ScanScreen.tsx +341 -0
- package/src/services/classifier.js +122 -0
- package/src/services/classifier.ts +83 -0
- package/src/services/diseaseData.js +191 -0
- package/src/services/diseaseData.ts +192 -0
- package/src/services/index.js +19 -0
- package/src/services/index.ts +3 -0
- package/src/theme/index.js +51 -0
- package/src/theme/index.ts +51 -0
- package/src/utils/imageToTensor.js +80 -0
- package/src/utils/imageToTensor.ts +36 -0
- package/src/utils/index.js +18 -0
- package/src/utils/index.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# 🌿 plant-health-lib
|
|
2
|
+
|
|
3
|
+
A comprehensive React Native library for on-device plant disease detection. Point the camera at a leaf (or upload a photo) and a quantized TFLite model classifies 38+ crop diseases — fully offline, no backend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install plant-health-lib
|
|
9
|
+
# or
|
|
10
|
+
yarn add plant-health-lib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { useFrameClassifier, useHistory } from 'plant-health-lib/hooks';
|
|
17
|
+
import { getDiseaseInfo } from 'plant-health-lib/services';
|
|
18
|
+
import { classifyImage } from 'plant-health-lib/utils';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- ✅ **38+ Disease Classes** – Comprehensive crop disease database
|
|
24
|
+
- ✅ **TensorFlow Lite Integration** – Fast GPU-accelerated inference
|
|
25
|
+
- ✅ **Live Camera Detection** – Real-time classification from camera frames
|
|
26
|
+
- ✅ **Offline Operation** – No backend required, fully on-device
|
|
27
|
+
- ✅ **TypeScript Support** – Full type safety included
|
|
28
|
+
- ✅ **History Tracking** – AsyncStorage-backed scan history
|
|
29
|
+
|
|
30
|
+
## Stack
|
|
31
|
+
|
|
32
|
+
| Concern | Library |
|
|
33
|
+
|---|---|
|
|
34
|
+
| AI inference | `react-native-fast-tflite` (GPU/CoreML delegates) |
|
|
35
|
+
| Camera + live frames | `react-native-vision-camera` + `vision-camera-resize-plugin` |
|
|
36
|
+
| Frame-processor threading | `react-native-worklets-core`, `react-native-reanimated` |
|
|
37
|
+
| Navigation | `@react-navigation/native-stack` |
|
|
38
|
+
| Persistence | `@react-native-async-storage/async-storage` |
|
|
39
|
+
| Gallery upload | `react-native-image-picker` |
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
App.tsx warms up the model on launch
|
|
45
|
+
src/
|
|
46
|
+
navigation/RootNavigator Home → Scan → Result, + History
|
|
47
|
+
screens/
|
|
48
|
+
HomeScreen dashboard, recent scans, entry points
|
|
49
|
+
ScanScreen live camera w/ throttled frame classification + capture/upload
|
|
50
|
+
ResultScreen diagnosis, confidence, treatment, prevention, top-K
|
|
51
|
+
HistoryScreen saved scans
|
|
52
|
+
services/
|
|
53
|
+
classifier.ts loads .tflite, normalizes input, runs inference, softmax + top-K
|
|
54
|
+
diseaseData.ts 38-class label map + remedy/prevention knowledge base
|
|
55
|
+
hooks/
|
|
56
|
+
useFrameClassifier.ts vision-camera frame processor → resize (GPU) → TFLite on worklet
|
|
57
|
+
useHistory.ts AsyncStorage-backed scan history
|
|
58
|
+
utils/imageToTensor.ts still-image decode + resize to 224×224×3
|
|
59
|
+
components/ui.tsx Card, Button, SeverityBadge, ConfidenceBar
|
|
60
|
+
theme/ design tokens
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Two inference paths:** live camera frames run through the GPU resize plugin inside a worklet (fast, ~1 fps throttled); still captures/uploads go through `imageToTensor`. Both feed the same `classifier.classify()`.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Using the Frame Classifier Hook (Live Camera)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { useFrameClassifier } from 'plant-health-lib/hooks';
|
|
71
|
+
import { getDiseaseInfo } from 'plant-health-lib/services';
|
|
72
|
+
|
|
73
|
+
export function ScanComponent() {
|
|
74
|
+
const { classify, isLoading, lastResult } = useFrameClassifier();
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (lastResult) {
|
|
78
|
+
const disease = getDiseaseInfo(lastResult.label);
|
|
79
|
+
console.log(`Detected: ${disease.name} (${lastResult.confidence}%)`);
|
|
80
|
+
}
|
|
81
|
+
}, [lastResult]);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<CameraView
|
|
85
|
+
onFrame={(frame) => classify(frame)}
|
|
86
|
+
// ... camera props
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Using the History Hook
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { useHistory } from 'plant-health-lib/hooks';
|
|
96
|
+
|
|
97
|
+
const { scans, addScan, clearHistory } = useHistory();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Classifying Images
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { classifyImage } from 'plant-health-lib/utils';
|
|
104
|
+
import { classifier } from 'plant-health-lib/services';
|
|
105
|
+
|
|
106
|
+
const result = await classifyImage(imagePath);
|
|
107
|
+
// result: { label: string; confidence: number; }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## API Reference
|
|
111
|
+
|
|
112
|
+
### Hooks
|
|
113
|
+
|
|
114
|
+
- **`useFrameClassifier()`** – Real-time frame classification from camera
|
|
115
|
+
- Returns: `{ classify(frame), isLoading, lastResult }`
|
|
116
|
+
|
|
117
|
+
- **`useHistory()`** – Manage scan history with AsyncStorage
|
|
118
|
+
- Returns: `{ scans, addScan(result), clearHistory() }`
|
|
119
|
+
|
|
120
|
+
### Services
|
|
121
|
+
|
|
122
|
+
- **`classifier.classify(tensor)`** – Classify a TensorFlow tensor
|
|
123
|
+
- **`getDiseaseInfo(label)`** – Get remedy/prevention data for a disease
|
|
124
|
+
|
|
125
|
+
### Utils
|
|
126
|
+
|
|
127
|
+
- **`classifyImage(path)`** – Load and classify a still image from file path
|
|
128
|
+
|
|
129
|
+
### Components
|
|
130
|
+
|
|
131
|
+
- **`<UI.Card />`** – Styled card component
|
|
132
|
+
- **`<UI.Button />`** – Button component
|
|
133
|
+
- **`<UI.SeverityBadge />`** – Disease severity indicator
|
|
134
|
+
- **`<UI.ConfidenceBar />`** – Confidence visualization
|
|
135
|
+
|
|
136
|
+
## Setting Up the Model
|
|
137
|
+
|
|
138
|
+
The library expects `src/assets/plant_disease_model.tflite`:
|
|
139
|
+
- **Input:** `[1, 224, 224, 3]` float32
|
|
140
|
+
- **Output:** `[1, 38]` (logits or probabilities)
|
|
141
|
+
- **Labels:** Must match `LABELS` in `src/services/diseaseData.ts`
|
|
142
|
+
|
|
143
|
+
Train on PlantVillage dataset using MobileNetV2 or EfficientNet-Lite, then convert with TFLite converter. See `src/assets/README.md` for details.
|
|
144
|
+
|
|
145
|
+
## Building from Source
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm install
|
|
149
|
+
npm run build
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The compiled library will be in `lib/`.
|
|
153
|
+
|
|
154
|
+
## Contributing
|
|
155
|
+
|
|
156
|
+
We welcome contributions! Please feel free to submit a Pull Request.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
|
|
162
|
+
## Notes / Production Hardening
|
|
163
|
+
|
|
164
|
+
- The remedy text is general guidance — maintain an on-screen disclaimer; it's not a substitute for professional agricultural advice.
|
|
165
|
+
- For production, replace the JS image resampler with a native bitmap decoder for better performance.
|
|
166
|
+
- Tune `MEAN`/`STD` in `classifier.ts` to match your training normalization.
|
|
167
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AACA,cAAc,MAAM,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Export all components
|
|
18
|
+
__exportStar(require("./ui"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wBAAwB;AACxB,uCAAqB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewStyle } from 'react-native';
|
|
3
|
+
import type { Severity } from '../services/diseaseData';
|
|
4
|
+
export declare const TrashIcon: React.FC<{
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const Card: React.FC<{
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
style?: ViewStyle;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const Button: React.FC<{
|
|
13
|
+
label: string;
|
|
14
|
+
onPress: () => void;
|
|
15
|
+
variant?: 'primary' | 'ghost' | 'danger';
|
|
16
|
+
loading?: boolean;
|
|
17
|
+
icon?: React.ReactNode;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const SeverityBadge: React.FC<{
|
|
20
|
+
severity: Severity;
|
|
21
|
+
}>;
|
|
22
|
+
export declare const ConfidenceBar: React.FC<{
|
|
23
|
+
value: number;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const ConfirmSheet: React.FC<{
|
|
26
|
+
visible: boolean;
|
|
27
|
+
title: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
confirmLabel?: string;
|
|
30
|
+
onConfirm: () => void;
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
}>;
|
|
33
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/components/ui.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAKL,SAAS,EAIV,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjE,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,CAGlB,CAAC;AAE3D,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB,CA4BA,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAO1D,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CASrD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAwCA,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.ConfirmSheet = exports.ConfidenceBar = exports.SeverityBadge = exports.Button = exports.Card = exports.TrashIcon = void 0;
|
|
27
|
+
const react_1 = __importStar(require("react"));
|
|
28
|
+
const react_native_1 = require("react-native");
|
|
29
|
+
const react_native_svg_1 = __importStar(require("react-native-svg"));
|
|
30
|
+
const theme_1 = require("../theme");
|
|
31
|
+
const TrashIcon = ({ size = 22, color = theme_1.colors.danger, }) => (<react_native_svg_1.default width={size} height={size} viewBox="0 0 22 20" fill="none">
|
|
32
|
+
<react_native_svg_1.Path d="M3 6h18" stroke={color} strokeWidth={1.8} strokeLinecap="round"/>
|
|
33
|
+
<react_native_svg_1.Path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2" stroke={color} strokeWidth={1.8} strokeLinecap="round"/>
|
|
34
|
+
<react_native_svg_1.Rect x="5" y="6" width="14" height="14" rx="2" stroke={color} strokeWidth={1.8}/>
|
|
35
|
+
<react_native_svg_1.Line x1="10" y1="11" x2="10" y2="17" stroke={color} strokeWidth={1.8} strokeLinecap="round"/>
|
|
36
|
+
<react_native_svg_1.Line x1="14" y1="11" x2="14" y2="17" stroke={color} strokeWidth={1.8} strokeLinecap="round"/>
|
|
37
|
+
</react_native_svg_1.default>);
|
|
38
|
+
exports.TrashIcon = TrashIcon;
|
|
39
|
+
const Card = ({ children, style, }) => <react_native_1.View style={[styles.card, style]}>{children}</react_native_1.View>;
|
|
40
|
+
exports.Card = Card;
|
|
41
|
+
const Button = ({ label, onPress, variant = 'primary', loading, icon }) => {
|
|
42
|
+
const bg = variant === 'primary' ? theme_1.colors.primary : variant === 'danger' ? theme_1.colors.danger : 'transparent';
|
|
43
|
+
const fg = variant === 'ghost' ? theme_1.colors.text : '#08130C';
|
|
44
|
+
return (<react_native_1.TouchableOpacity activeOpacity={0.85} onPress={onPress} disabled={loading} style={[
|
|
45
|
+
styles.btn,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: bg,
|
|
48
|
+
borderWidth: variant === 'ghost' ? 1 : 0,
|
|
49
|
+
borderColor: theme_1.colors.border,
|
|
50
|
+
},
|
|
51
|
+
]}>
|
|
52
|
+
{loading ? (<react_native_1.ActivityIndicator color={fg}/>) : (<react_native_1.View style={styles.btnRow}>
|
|
53
|
+
{icon}
|
|
54
|
+
<react_native_1.Text style={[styles.btnText, { color: fg }]}>{label}</react_native_1.Text>
|
|
55
|
+
</react_native_1.View>)}
|
|
56
|
+
</react_native_1.TouchableOpacity>);
|
|
57
|
+
};
|
|
58
|
+
exports.Button = Button;
|
|
59
|
+
const SeverityBadge = ({ severity }) => (<react_native_1.View style={[styles.badge, { backgroundColor: (0, theme_1.severityColor)(severity) + '22' }]}>
|
|
60
|
+
<react_native_1.View style={[styles.dot, { backgroundColor: (0, theme_1.severityColor)(severity) }]}/>
|
|
61
|
+
<react_native_1.Text style={[styles.badgeText, { color: (0, theme_1.severityColor)(severity) }]}>
|
|
62
|
+
{severity.toUpperCase()}
|
|
63
|
+
</react_native_1.Text>
|
|
64
|
+
</react_native_1.View>);
|
|
65
|
+
exports.SeverityBadge = SeverityBadge;
|
|
66
|
+
const ConfidenceBar = ({ value }) => (<react_native_1.View style={styles.barTrack}>
|
|
67
|
+
<react_native_1.View style={[
|
|
68
|
+
styles.barFill,
|
|
69
|
+
{ width: `${Math.round(value * 100)}%`, backgroundColor: theme_1.colors.primary },
|
|
70
|
+
]}/>
|
|
71
|
+
</react_native_1.View>);
|
|
72
|
+
exports.ConfidenceBar = ConfidenceBar;
|
|
73
|
+
const ConfirmSheet = ({ visible, title, message, confirmLabel = 'Delete', onConfirm, onClose }) => {
|
|
74
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
75
|
+
const translateY = (0, react_1.useRef)(new react_native_1.Animated.Value(300)).current;
|
|
76
|
+
const backdropOpacity = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
77
|
+
(0, react_1.useEffect)(() => {
|
|
78
|
+
if (visible) {
|
|
79
|
+
setOpen(true);
|
|
80
|
+
react_native_1.Animated.parallel([
|
|
81
|
+
react_native_1.Animated.spring(translateY, { toValue: 0, useNativeDriver: true, bounciness: 4 }),
|
|
82
|
+
react_native_1.Animated.timing(backdropOpacity, { toValue: 1, duration: 200, useNativeDriver: true }),
|
|
83
|
+
]).start();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
react_native_1.Animated.parallel([
|
|
87
|
+
react_native_1.Animated.timing(translateY, { toValue: 300, duration: 200, useNativeDriver: true }),
|
|
88
|
+
react_native_1.Animated.timing(backdropOpacity, { toValue: 0, duration: 200, useNativeDriver: true }),
|
|
89
|
+
]).start(() => setOpen(false));
|
|
90
|
+
}
|
|
91
|
+
}, [visible]);
|
|
92
|
+
return (<react_native_1.Modal visible={open} transparent animationType="none" onRequestClose={onClose}>
|
|
93
|
+
<react_native_1.View style={react_native_1.StyleSheet.absoluteFill}>
|
|
94
|
+
<react_native_1.Animated.View style={[styles.sheetBackdrop, { opacity: backdropOpacity }]}>
|
|
95
|
+
<react_native_1.TouchableOpacity style={{ flex: 1 }} onPress={onClose} activeOpacity={1}/>
|
|
96
|
+
</react_native_1.Animated.View>
|
|
97
|
+
<react_native_1.View style={{ flex: 1, justifyContent: 'flex-end' }}>
|
|
98
|
+
<react_native_1.Animated.View style={[styles.sheet, { transform: [{ translateY }] }]}>
|
|
99
|
+
<react_native_1.View style={styles.handle}/>
|
|
100
|
+
<react_native_1.Text style={styles.sheetTitle}>{title}</react_native_1.Text>
|
|
101
|
+
{message ? <react_native_1.Text style={styles.sheetMessage}>{message}</react_native_1.Text> : null}
|
|
102
|
+
<react_native_1.View style={styles.sheetActions}>
|
|
103
|
+
<exports.Button label={confirmLabel} onPress={onConfirm} variant="danger"/>
|
|
104
|
+
<exports.Button label="Cancel" onPress={onClose} variant="ghost"/>
|
|
105
|
+
</react_native_1.View>
|
|
106
|
+
</react_native_1.Animated.View>
|
|
107
|
+
</react_native_1.View>
|
|
108
|
+
</react_native_1.View>
|
|
109
|
+
</react_native_1.Modal>);
|
|
110
|
+
};
|
|
111
|
+
exports.ConfirmSheet = ConfirmSheet;
|
|
112
|
+
const styles = react_native_1.StyleSheet.create({
|
|
113
|
+
card: {
|
|
114
|
+
backgroundColor: theme_1.colors.surface,
|
|
115
|
+
borderRadius: theme_1.radius.lg,
|
|
116
|
+
padding: theme_1.spacing.lg,
|
|
117
|
+
borderWidth: 1,
|
|
118
|
+
borderColor: theme_1.colors.border,
|
|
119
|
+
},
|
|
120
|
+
btn: {
|
|
121
|
+
borderRadius: theme_1.radius.pill,
|
|
122
|
+
paddingVertical: theme_1.spacing.md,
|
|
123
|
+
paddingHorizontal: theme_1.spacing.lg,
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
justifyContent: 'center',
|
|
126
|
+
},
|
|
127
|
+
btnRow: { flexDirection: 'row', alignItems: 'center', gap: theme_1.spacing.sm },
|
|
128
|
+
btnText: { fontSize: theme_1.font.body, fontWeight: '700' },
|
|
129
|
+
badge: {
|
|
130
|
+
flexDirection: 'row',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: theme_1.spacing.xs,
|
|
133
|
+
paddingHorizontal: theme_1.spacing.sm,
|
|
134
|
+
paddingVertical: 4,
|
|
135
|
+
borderRadius: theme_1.radius.pill,
|
|
136
|
+
alignSelf: 'flex-start',
|
|
137
|
+
},
|
|
138
|
+
dot: { width: 8, height: 8, borderRadius: 4 },
|
|
139
|
+
badgeText: { fontSize: theme_1.font.tiny, fontWeight: '800', letterSpacing: 0.5 },
|
|
140
|
+
barTrack: {
|
|
141
|
+
height: 8,
|
|
142
|
+
backgroundColor: theme_1.colors.surfaceAlt,
|
|
143
|
+
borderRadius: theme_1.radius.pill,
|
|
144
|
+
overflow: 'hidden',
|
|
145
|
+
},
|
|
146
|
+
barFill: { height: 8, borderRadius: theme_1.radius.pill },
|
|
147
|
+
sheetBackdrop: {
|
|
148
|
+
...react_native_1.StyleSheet.absoluteFillObject,
|
|
149
|
+
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
150
|
+
},
|
|
151
|
+
sheet: {
|
|
152
|
+
backgroundColor: theme_1.colors.surface,
|
|
153
|
+
borderTopLeftRadius: theme_1.radius.lg,
|
|
154
|
+
borderTopRightRadius: theme_1.radius.lg,
|
|
155
|
+
borderWidth: 1,
|
|
156
|
+
borderColor: theme_1.colors.border,
|
|
157
|
+
paddingHorizontal: theme_1.spacing.lg,
|
|
158
|
+
paddingBottom: theme_1.spacing.xl,
|
|
159
|
+
paddingTop: theme_1.spacing.md,
|
|
160
|
+
},
|
|
161
|
+
handle: {
|
|
162
|
+
width: 40,
|
|
163
|
+
height: 4,
|
|
164
|
+
borderRadius: theme_1.radius.pill,
|
|
165
|
+
backgroundColor: theme_1.colors.border,
|
|
166
|
+
alignSelf: 'center',
|
|
167
|
+
marginBottom: theme_1.spacing.lg,
|
|
168
|
+
},
|
|
169
|
+
sheetTitle: {
|
|
170
|
+
color: theme_1.colors.text,
|
|
171
|
+
fontSize: theme_1.font.h3,
|
|
172
|
+
fontWeight: '700',
|
|
173
|
+
textAlign: 'center',
|
|
174
|
+
marginBottom: theme_1.spacing.xs,
|
|
175
|
+
},
|
|
176
|
+
sheetMessage: {
|
|
177
|
+
color: theme_1.colors.textDim,
|
|
178
|
+
fontSize: theme_1.font.body,
|
|
179
|
+
textAlign: 'center',
|
|
180
|
+
marginBottom: theme_1.spacing.md,
|
|
181
|
+
},
|
|
182
|
+
sheetActions: { gap: theme_1.spacing.sm, marginTop: theme_1.spacing.lg },
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/components/ui.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Prediction } from '../services/classifier';
|
|
3
|
+
export interface ScanRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
uri: string;
|
|
6
|
+
label: string;
|
|
7
|
+
crop: string;
|
|
8
|
+
name: string;
|
|
9
|
+
healthy: boolean;
|
|
10
|
+
severity: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
topK: {
|
|
13
|
+
label: string;
|
|
14
|
+
confidence: number;
|
|
15
|
+
}[];
|
|
16
|
+
date: number;
|
|
17
|
+
}
|
|
18
|
+
interface HistoryContextValue {
|
|
19
|
+
records: ScanRecord[];
|
|
20
|
+
loaded: boolean;
|
|
21
|
+
add: (uri: string, p: Prediction) => ScanRecord;
|
|
22
|
+
remove: (id: string) => void;
|
|
23
|
+
clear: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function HistoryProvider({ children }: {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}): React.JSX.Element;
|
|
28
|
+
export declare function useHistoryContext(): HistoryContextValue;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=HistoryContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HistoryContext.d.ts","sourceRoot":"","sources":["../../src/contexts/HistoryContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAE3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,mBAAmB;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,KAAK,UAAU,CAAC;IAChD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAID,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAgD1E;AAED,wBAAgB,iBAAiB,IAAI,mBAAmB,CAIvD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.HistoryProvider = HistoryProvider;
|
|
30
|
+
exports.useHistoryContext = useHistoryContext;
|
|
31
|
+
const react_1 = __importStar(require("react"));
|
|
32
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
33
|
+
const KEY = '@plantdoctor/history';
|
|
34
|
+
const HistoryContext = (0, react_1.createContext)(null);
|
|
35
|
+
function HistoryProvider({ children }) {
|
|
36
|
+
const [records, setRecords] = (0, react_1.useState)([]);
|
|
37
|
+
const [loaded, setLoaded] = (0, react_1.useState)(false);
|
|
38
|
+
(0, react_1.useEffect)(() => {
|
|
39
|
+
async_storage_1.default.getItem(KEY)
|
|
40
|
+
.then((raw) => setRecords(raw ? JSON.parse(raw) : []))
|
|
41
|
+
.catch(() => setRecords([]))
|
|
42
|
+
.finally(() => setLoaded(true));
|
|
43
|
+
}, []);
|
|
44
|
+
const persist = (0, react_1.useCallback)(async (next) => {
|
|
45
|
+
setRecords(next);
|
|
46
|
+
await async_storage_1.default.setItem(KEY, JSON.stringify(next));
|
|
47
|
+
}, []);
|
|
48
|
+
const add = (0, react_1.useCallback)((uri, p) => {
|
|
49
|
+
const rec = {
|
|
50
|
+
id: `${Date.now()}`,
|
|
51
|
+
uri,
|
|
52
|
+
label: p.info.label,
|
|
53
|
+
crop: p.info.crop,
|
|
54
|
+
name: p.info.name,
|
|
55
|
+
healthy: p.info.healthy,
|
|
56
|
+
severity: p.info.severity,
|
|
57
|
+
confidence: p.confidence,
|
|
58
|
+
topK: p.topK,
|
|
59
|
+
date: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
persist([rec, ...records].slice(0, 100));
|
|
62
|
+
return rec;
|
|
63
|
+
}, [records, persist]);
|
|
64
|
+
const remove = (0, react_1.useCallback)((id) => persist(records.filter((r) => r.id !== id)), [records, persist]);
|
|
65
|
+
const clear = (0, react_1.useCallback)(() => persist([]), [persist]);
|
|
66
|
+
return (<HistoryContext.Provider value={{ records, loaded, add, remove, clear }}>
|
|
67
|
+
{children}
|
|
68
|
+
</HistoryContext.Provider>);
|
|
69
|
+
}
|
|
70
|
+
function useHistoryContext() {
|
|
71
|
+
const ctx = (0, react_1.useContext)(HistoryContext);
|
|
72
|
+
if (!ctx)
|
|
73
|
+
throw new Error('useHistoryContext must be used within HistoryProvider');
|
|
74
|
+
return ctx;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=HistoryContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HistoryContext.js","sourceRoot":"","sources":["../../src/contexts/HistoryContext.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useHistory = exports.useFrameClassifier = void 0;
|
|
4
|
+
// Export all hooks
|
|
5
|
+
var useFrameClassifier_1 = require("./useFrameClassifier");
|
|
6
|
+
Object.defineProperty(exports, "useFrameClassifier", { enumerable: true, get: function () { return useFrameClassifier_1.useFrameClassifier; } });
|
|
7
|
+
var useHistory_1 = require("./useHistory");
|
|
8
|
+
Object.defineProperty(exports, "useHistory", { enumerable: true, get: function () { return useHistory_1.useHistory; } });
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,2CAA0C;AAAjC,wGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Prediction } from '../services/classifier';
|
|
2
|
+
export declare function useFrameClassifier(enabled: boolean, intervalMs?: number): {
|
|
3
|
+
frameProcessor: import("react-native-vision-camera").ReadonlyFrameProcessor;
|
|
4
|
+
prediction: Prediction | null;
|
|
5
|
+
running: boolean;
|
|
6
|
+
setRunning: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
7
|
+
isPlantDetected: boolean;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=useFrameClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFrameClassifier.d.ts","sourceRoot":"","sources":["../../src/hooks/useFrameClassifier.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgC,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAuBlF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,SAAO;;;;;;EAoDrE"}
|