feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,324 @@
// Copyright 2022-present 650 Industries. All rights reserved.
import AVFoundation
import ExpoModulesCore
import VisionKit
let cameraEvents = ["onCameraReady", "onMountError", "onPictureSaved", "onBarcodeScanned", "onResponsiveOrientationChanged"]
struct ScannerContext {
var controller: Any?
var delegate: Any?
}
public final class CameraViewModule: Module, ScannerResultHandler {
private var scannerContext: ScannerContext?
public func definition() -> ModuleDefinition {
Name("ExpoCamera")
Events("onModernBarcodeScanned")
OnCreate {
let permissionsManager = self.appContext?.permissions
EXPermissionsMethodsDelegate.register(
[
CameraOnlyPermissionRequester(),
CameraMicrophonePermissionRequester()
],
withPermissionsManager: permissionsManager
)
}
Property("isModernBarcodeScannerAvailable") { () -> Bool in
if #available(iOS 16.0, *) {
return true
}
return false
}
AsyncFunction("scanFromURLAsync") { (url: URL, _: [BarcodeType], promise: Promise) in
guard let imageLoader = appContext?.imageLoader else {
throw ImageLoaderNotFound()
}
imageLoader.loadImage(for: url) { error, image in
if error != nil {
promise.reject(FailedToLoadImage())
return
}
guard let cgImage = image?.cgImage else {
promise.reject(FailedToLoadImage())
return
}
guard let detector = CIDetector(
ofType: CIDetectorTypeQRCode,
context: nil,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
) else {
promise.reject(InitScannerFailed())
return
}
let ciImage = CIImage(cgImage: cgImage)
let features = detector.features(in: ciImage)
promise.resolve(BarcodeUtils.getResultFrom(features))
}
}
// swiftlint:disable:next closure_body_length
View(CameraView.self) {
Events(cameraEvents)
Prop("facing") { (view, type: CameraType?) in
if let type, view.presetCamera != type.toPosition() {
view.presetCamera = type.toPosition()
}
}
Prop("flashMode") { (view, flashMode: FlashMode?) in
if let flashMode, view.flashMode != flashMode {
view.flashMode = flashMode
}
}
Prop("enableTorch") { (view, enabled: Bool?) in
view.torchEnabled = enabled ?? false
}
Prop("pictureSize") { (view, pictureSize: PictureSize?) in
if let pictureSize, view.pictureSize != pictureSize {
view.pictureSize = pictureSize
}
}
Prop("zoom") { (view, zoom: Double?) in
if let zoom, fabs(view.zoom - zoom) > Double.ulpOfOne {
view.zoom = zoom
}
}
Prop("mode") { (view, mode: CameraMode?) in
if let mode, view.mode != mode {
view.mode = mode
}
}
Prop("barcodeScannerEnabled") { (view, scanBarcodes: Bool?) in
if let scanBarcodes, view.isScanningBarcodes != scanBarcodes {
view.isScanningBarcodes = scanBarcodes
}
}
Prop("barcodeScannerSettings") { (view, settings: BarcodeSettings?) in
if let settings {
view.setBarcodeScannerSettings(settings: settings)
}
}
Prop("mute") { (view, muted: Bool?) in
view.isMuted = muted ?? false
}
Prop("animateShutter") { (view, animate: Bool?) in
view.animateShutter = animate ?? true
}
Prop("videoQuality") { (view, quality: VideoQuality?) in
if let quality, view.videoQuality != quality {
view.videoQuality = quality
}
}
Prop("autoFocus") { (view, focusMode: FocusMode?) in
view.autoFocus = focusMode?.toAVCaptureFocusMode() ?? .continuousAutoFocus
}
Prop("responsiveOrientationWhenOrientationLocked") { (view, responsiveOrientation: Bool?) in
if let responsiveOrientation, view.responsiveWhenOrientationLocked != responsiveOrientation {
view.responsiveWhenOrientationLocked = responsiveOrientation
}
}
Prop("mirror") { (view, mirror: Bool?) in
if let mirror {
view.mirror = mirror
return
}
view.mirror = false
}
Prop("active") { (view, active: Bool?) in
if let active {
view.active = active
return
}
view.active = true
}
OnViewDidUpdateProps { view in
view.initCamera()
}
AsyncFunction("resumePreview") { view in
view.resumePreview()
}
AsyncFunction("pausePreview") { view in
view.pausePreview()
}
AsyncFunction("getAvailablePictureSizes") { (_: String?) in
return PictureSize.allCases.map {
$0.rawValue
}
}
AsyncFunction("takePicture") { (view, options: TakePictureOptions, promise: Promise) in
#if targetEnvironment(simulator) // simulator
try takePictureForSimulator(self.appContext, view, options, promise)
#else // not simulator
view.takePicture(options: options, promise: promise)
#endif
}.runOnQueue(.main)
AsyncFunction("record") { (view, options: CameraRecordingOptions, promise: Promise) in
#if targetEnvironment(simulator)
throw Exceptions.SimulatorNotSupported()
#else
view.record(options: options, promise: promise)
#endif
}.runOnQueue(.main)
AsyncFunction("stopRecording") { view in
#if targetEnvironment(simulator)
throw Exceptions.SimulatorNotSupported()
#else
view.stopRecording()
#endif
}.runOnQueue(.main)
}
AsyncFunction("launchScanner") { (options: VisionScannerOptions?) in
if #available(iOS 16.0, *) {
await MainActor.run {
let delegate = VisionScannerDelegate(handler: self)
scannerContext = ScannerContext(delegate: delegate)
launchScanner(with: options)
}
}
}
AsyncFunction("dismissScanner") {
if #available(iOS 16.0, *) {
await MainActor.run {
dismissScanner()
}
}
}
AsyncFunction("getCameraPermissionsAsync") { (promise: Promise) in
EXPermissionsMethodsDelegate.getPermissionWithPermissionsManager(
self.appContext?.permissions,
withRequester: CameraOnlyPermissionRequester.self,
resolve: promise.resolver,
reject: promise.legacyRejecter
)
}
AsyncFunction("requestCameraPermissionsAsync") { (promise: Promise) in
EXPermissionsMethodsDelegate.askForPermission(
withPermissionsManager: self.appContext?.permissions,
withRequester: CameraOnlyPermissionRequester.self,
resolve: promise.resolver,
reject: promise.legacyRejecter
)
}
AsyncFunction("getMicrophonePermissionsAsync") { (promise: Promise) in
EXPermissionsMethodsDelegate.getPermissionWithPermissionsManager(
self.appContext?.permissions,
withRequester: CameraMicrophonePermissionRequester.self,
resolve: promise.resolver,
reject: promise.legacyRejecter
)
}
AsyncFunction("requestMicrophonePermissionsAsync") { (promise: Promise) in
EXPermissionsMethodsDelegate.askForPermission(
withPermissionsManager: self.appContext?.permissions,
withRequester: CameraMicrophonePermissionRequester.self,
resolve: promise.resolver,
reject: promise.legacyRejecter
)
}
AsyncFunction("getAvailableVideoCodecsAsync") { () -> [String] in
return getAvailableVideoCodecs()
}
}
@available(iOS 16.0, *)
@MainActor
private func launchScanner(with options: VisionScannerOptions?) {
let symbologies = options?.toSymbology() ?? [.qr]
let controller = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: symbologies)],
isPinchToZoomEnabled: options?.isPinchToZoomEnabled ?? true,
isGuidanceEnabled: options?.isGuidanceEnabled ?? true,
isHighlightingEnabled: options?.isHighlightingEnabled ?? false
)
scannerContext?.controller = controller
if let delegate = scannerContext?.delegate as? VisionScannerDelegate {
controller.delegate = delegate
}
appContext?.utilities?.currentViewController()?.present(controller, animated: true) {
try? controller.startScanning()
}
}
@available(iOS 16.0, *)
@MainActor
private func dismissScanner() {
guard let controller = scannerContext?.controller as? DataScannerViewController else {
return
}
controller.stopScanning()
controller.dismiss(animated: true)
}
func onItemScanned(result: [String: Any]) {
sendEvent("onModernBarcodeScanned", result)
}
private func getAvailableVideoCodecs() -> [String] {
let session = AVCaptureSession()
session.beginConfiguration()
guard let captureDevice = ExpoCameraUtils.device(
with: AVMediaType.video,
preferring: AVCaptureDevice.Position.front) else {
return []
}
guard let deviceInput = try? AVCaptureDeviceInput(device: captureDevice) else {
return []
}
if session.canAddInput(deviceInput) {
session.addInput(deviceInput)
}
session.commitConfiguration()
let movieFileOutput = AVCaptureMovieFileOutput()
if session.canAddOutput(movieFileOutput) {
session.addOutput(movieFileOutput)
}
return movieFileOutput.availableVideoCodecTypes.map { $0.rawValue }
}
}