- 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
137 lines
4.5 KiB
Swift
137 lines
4.5 KiB
Swift
// Copyright 2022-present 650 Industries. All rights reserved.
|
|
|
|
import Photos
|
|
import ExpoModulesCore
|
|
|
|
public class CameraPermissionRequester: NSObject, EXPermissionsRequester {
|
|
static public func permissionType() -> String {
|
|
return "camera"
|
|
}
|
|
|
|
public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: EXPromiseRejectBlock) {
|
|
AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] _ in
|
|
resolve(self?.getPermissions())
|
|
}
|
|
}
|
|
|
|
public func getPermissions() -> [AnyHashable: Any] {
|
|
var systemStatus: AVAuthorizationStatus
|
|
var status: EXPermissionStatus
|
|
let cameraUsageDescription = Bundle.main.object(forInfoDictionaryKey: "NSCameraUsageDescription")
|
|
if cameraUsageDescription == nil {
|
|
EXFatal(EXErrorWithMessage("""
|
|
This app is missing 'NSCameraUsageDescription', video services will fail. \
|
|
Ensure this key exists in the app's Info.plist
|
|
"""))
|
|
systemStatus = AVAuthorizationStatus.denied
|
|
} else {
|
|
systemStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
|
|
}
|
|
|
|
switch systemStatus {
|
|
case .authorized:
|
|
status = EXPermissionStatusGranted
|
|
case .restricted,
|
|
.denied:
|
|
status = EXPermissionStatusDenied
|
|
case .notDetermined:
|
|
fallthrough
|
|
@unknown default:
|
|
status = EXPermissionStatusUndetermined
|
|
}
|
|
|
|
return [
|
|
"status": status.rawValue
|
|
]
|
|
}
|
|
}
|
|
|
|
public class MediaLibraryPermissionRequester: DefaultMediaLibraryPermissionRequester,
|
|
EXPermissionsRequester {
|
|
public static func permissionType() -> String {
|
|
return "mediaLibrary"
|
|
}
|
|
}
|
|
|
|
public class MediaLibraryWriteOnlyPermissionRequester: DefaultMediaLibraryPermissionRequester,
|
|
EXPermissionsRequester {
|
|
public static func permissionType() -> String {
|
|
return "mediaLibraryWriteOnly"
|
|
}
|
|
|
|
@available(iOS 14, *)
|
|
override internal func accessLevel() -> PHAccessLevel {
|
|
return PHAccessLevel.addOnly
|
|
}
|
|
}
|
|
|
|
// MARK: - Permission requesters shared implementation extracted to an extension (mixin pattern)
|
|
|
|
/**
|
|
* Dummy class just to prevent extending NSObject publicly/globally.
|
|
*/
|
|
public class DefaultMediaLibraryPermissionRequester: NSObject {}
|
|
|
|
/**
|
|
* This extension is adding default implmentation for EXPermissionsRequester that can be shared by many classe.
|
|
* In Swift language you cannot override static methods in subclasses, so you cannot subclass any already implemented
|
|
* PermissionRequester as instances of this class are registered by the unique name coming from `static func permissionType()`.
|
|
* To prevent repeating the similar code for every MediaLibrary PermissionRequester (the only differences so far are
|
|
* aforementioned permissionType and accessLevel, while the latter can be easily overritten) I've extracted the code
|
|
* to this extension. I'm using as a mixin that implements major part of EXPermissionsRequester protocol.
|
|
*/
|
|
extension DefaultMediaLibraryPermissionRequester {
|
|
@objc
|
|
public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: EXPromiseRejectBlock) {
|
|
let authorizationHandler = { [weak self] (_: PHAuthorizationStatus) in
|
|
resolve(self?.getPermissions())
|
|
}
|
|
if #available(iOS 14.0, *) {
|
|
PHPhotoLibrary.requestAuthorization(for: self.accessLevel(), handler: authorizationHandler)
|
|
} else {
|
|
PHPhotoLibrary.requestAuthorization(authorizationHandler)
|
|
}
|
|
}
|
|
|
|
@objc
|
|
public func getPermissions() -> [AnyHashable: Any] {
|
|
var authorizationStatus: PHAuthorizationStatus
|
|
if #available(iOS 14.0, *) {
|
|
authorizationStatus = PHPhotoLibrary.authorizationStatus(for: self.accessLevel())
|
|
} else {
|
|
authorizationStatus = PHPhotoLibrary.authorizationStatus()
|
|
}
|
|
|
|
var status: EXPermissionStatus
|
|
var scope: String
|
|
|
|
switch authorizationStatus {
|
|
case .authorized:
|
|
status = EXPermissionStatusGranted
|
|
scope = "all"
|
|
case .limited:
|
|
status = EXPermissionStatusGranted
|
|
scope = "limited"
|
|
case .denied, .restricted:
|
|
status = EXPermissionStatusDenied
|
|
scope = "none"
|
|
case .notDetermined:
|
|
fallthrough
|
|
@unknown default:
|
|
status = EXPermissionStatusUndetermined
|
|
scope = "none"
|
|
}
|
|
|
|
return [
|
|
"status": status.rawValue,
|
|
"accessPrivileges": scope
|
|
]
|
|
}
|
|
|
|
@available(iOS 14, *)
|
|
@objc
|
|
internal func accessLevel() -> PHAccessLevel {
|
|
return PHAccessLevel.readWrite
|
|
}
|
|
}
|