From 1815c6ce51c202328e573de835a1b718c66627f8 Mon Sep 17 00:00:00 2001 From: duguyihou Date: Thu, 26 Oct 2023 21:42:36 +1100 Subject: [PATCH] 6 device info for ios (#7) * feat: wip * feat: wip * feat: storage * feat: battery * feat: constant * feat: network * feat: location * feat: headphone * feat: brightness * feat: typography * feat: web * feat: arch * feat: la * feat: others --- example/src/App.tsx | 15 ++- ios/TurboDevice+arch.swift | 10 ++ ios/TurboDevice+battery.swift | 79 +++++++++++++++ ios/TurboDevice+brightness.swift | 19 ++++ ios/TurboDevice+constant.swift | 105 ++++++++++++++++++++ ios/TurboDevice+headphone.swift | 33 +++++++ ios/TurboDevice+localAuthentication.swift | 16 ++++ ios/TurboDevice+location.swift | 35 +++++++ ios/TurboDevice+network.swift | 52 ++++++++++ ios/TurboDevice+storage.swift | 57 +++++++++++ ios/TurboDevice+typography.swift | 40 ++++++++ ios/TurboDevice+web.swift | 10 ++ ios/TurboDevice-Bridging-Header.h | 4 + ios/TurboDevice.mm | 73 +++++++++++++- ios/TurboDevice.swift | 100 ++++++++++++++++++- ios/UIDevice+utils.swift | 111 ++++++++++++++++++++++ src/index.tsx | 72 ++++++++++++++ 17 files changed, 825 insertions(+), 6 deletions(-) create mode 100644 ios/TurboDevice+arch.swift create mode 100644 ios/TurboDevice+battery.swift create mode 100644 ios/TurboDevice+brightness.swift create mode 100644 ios/TurboDevice+constant.swift create mode 100644 ios/TurboDevice+headphone.swift create mode 100644 ios/TurboDevice+localAuthentication.swift create mode 100644 ios/TurboDevice+location.swift create mode 100644 ios/TurboDevice+network.swift create mode 100644 ios/TurboDevice+storage.swift create mode 100644 ios/TurboDevice+typography.swift create mode 100644 ios/TurboDevice+web.swift create mode 100644 ios/UIDevice+utils.swift diff --git a/example/src/App.tsx b/example/src/App.tsx index fcb8f38..4ff0f42 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,18 +1,29 @@ import * as React from 'react'; import { StyleSheet, View, Text } from 'react-native'; -import { multiply } from 'react-native-turbo-device'; +import { getTotalDiskCapacity, multiply } from 'react-native-turbo-device'; export default function App() { const [result, setResult] = React.useState(); - + const [totalDiskCapacity, setTotalDiskCapacity] = React.useState< + number | undefined + >(); React.useEffect(() => { multiply(3, 7).then(setResult); }, []); + React.useEffect(() => { + const a = async () => { + const total = await getTotalDiskCapacity(); + setTotalDiskCapacity(total); + }; + a(); + }, []); + return ( Result: {result} + TotalDiskCapacity: {totalDiskCapacity} ); } diff --git a/ios/TurboDevice+arch.swift b/ios/TurboDevice+arch.swift new file mode 100644 index 0000000..82a2532 --- /dev/null +++ b/ios/TurboDevice+arch.swift @@ -0,0 +1,10 @@ +import Foundation +import MachO + +extension TurboDevice { + private func getSupportedAbis(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let archRaw = NXGetLocalArchInfo().pointee.name else { return resolve("unknown") } + resolve(String(cString: archRaw)) + } +} diff --git a/ios/TurboDevice+battery.swift b/ios/TurboDevice+battery.swift new file mode 100644 index 0000000..d430820 --- /dev/null +++ b/ios/TurboDevice+battery.swift @@ -0,0 +1,79 @@ +import Foundation + +extension TurboDevice { + + var powerState: [String:Any] { + +#if RCT_DEV && !targetEnvironment(simulator) && !os(tvOS) + if !UIDevice.current.isBatteryMonitoringEnabled { + RCTLogWarn("Battery monitoring is not enabled. You need to enable monitoring with `UIDevice.current.isBatteryMonitoringEnabled = true`") + } +#endif +#if RCT_DEV && targetEnvironment(simulator) && !os(tvOS) + if UIDevice.current.batteryState == .unknown { + RCTLogWarn("Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.") + } +#endif + let batteryLevel = UIDevice.current.batteryLevel +#if os(tvOS) + return [ + "batteryLevel": batteryLevel, + "batteryState": "full" + ] +#else + let batteryState = { + let state = UIDevice.current.batteryState + switch state { + case .full: + return "full" + case .charging: + return "charging" + case .unplugged: + return "unplugged" + default: + return "unknown" + } + }() + let lowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled + return [ + "batteryLevel": batteryLevel, + "batteryState": batteryState, + "lowPowerMode": lowPowerMode, + ] +#endif + } + + @objc + func batteryLevelDidChange(_ notification: Notification) { + let batteryLevel = UIDevice.current.batteryLevel + sendEvent(withName: "TurboDevice_batteryLevelDidChange", body: [batteryLevel]) + + if batteryLevel <= kLowBatteryThreshold { + sendEvent(withName: "TurboDevice_batteryLevelIsLow", body: [batteryLevel]) + } + } + + @objc + private func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if os(tvOS) + resolve(Float(1)) +#else + resolve(UIDevice.current.batteryLevel) +#endif + } + + @objc + func powerStateDidChange() { + sendEvent(withName: "TurboDevice_powerStateDidChange", body: [powerState]) + } + + @objc + func isBatteryCharging(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let batteryState = powerState["batteryState"] as? UIDevice.BatteryState + else { return reject("isBatteryCharging failed", nil, nil)} + let isCharging = batteryState == .charging + resolve(isCharging) + } +} diff --git a/ios/TurboDevice+brightness.swift b/ios/TurboDevice+brightness.swift new file mode 100644 index 0000000..13b85dd --- /dev/null +++ b/ios/TurboDevice+brightness.swift @@ -0,0 +1,19 @@ +import Foundation + +extension TurboDevice { + @objc + func brightnessDidChange() { + let brightness = UIScreen.main.brightness + sendEvent(withName: "TurboDevice_brightnessDidChange", body: [brightness]) + } + + @objc + func getBrightness(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if !os(tvOS) + resolve(UIScreen.main.brightness) +#else + resolve(CGFloat(-1)) +#endif + } +} diff --git a/ios/TurboDevice+constant.swift b/ios/TurboDevice+constant.swift new file mode 100644 index 0000000..4115484 --- /dev/null +++ b/ios/TurboDevice+constant.swift @@ -0,0 +1,105 @@ +import Foundation + +enum DeviceType: String { + case Handset = "Handset" + case Tablet = "Tablet" + case Tv = "Tv" + case Desktop = "Desktop" + case Unknown = "Unknown" + + func getDeviceTypeName() -> String { + switch self { + case .Handset: + return DeviceType.Handset.rawValue + case .Tablet: + return DeviceType.Tablet.rawValue + case .Tv: + return DeviceType.Tv.rawValue + case .Desktop: + return DeviceType.Desktop.rawValue + default: + return DeviceType.Unknown.rawValue + } + } +} + +extension TurboDevice { + func getBundleId() -> Any { + let buildId = Bundle.main.object(forInfoDictionaryKey: "CFBundleIdentifier") + return buildId ?? "unknown" + } + + func getSystemName() -> Any { + return UIDevice.current.systemName + } + + func getSystemVersion() -> Any { + return UIDevice.current.systemVersion + } + + func getAppVersion() -> Any { + let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") + return appVersion ?? "unknown" + } + + func getBuildNumber() -> Any { + let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") + return buildNumber ?? "unknown" + } + + func getAppName() -> Any { + let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") + let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName")! + + return displayName ?? bundleName + } + + func isTablet() -> Bool { + return getDeviceType() == .Tablet + } + + func getDeviceTypeName() -> Any { + let deviceType = getDeviceType() + let deviceTypeName = DeviceType.getDeviceTypeName(deviceType) + return deviceTypeName + } + + func getDeviceType() -> DeviceType { + let userInterfaceIdiom = UIDevice.current.userInterfaceIdiom + switch userInterfaceIdiom { + case .phone: + return .Handset + case .pad: +#if targetEnvironment(macCatalyst) + return .Desktop +#endif + if #available(iOS 14, *) { + if ProcessInfo.processInfo.isiOSAppOnMac { + return .Desktop + } + } + return .Tablet + case .tv: + return .Tv + case .mac: + return .Desktop + default: + return .Unknown + } + } + // TODO: - 🐵 use scene + func isDisplayZoomed() -> Bool { + return UIScreen.main.scale != UIScreen.main.nativeScale + } +} + +extension TurboDevice { + private func getBuildId() -> String { + #if os(tvOS) + return "unknown" + #else + let buildNumber = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String + return buildNumber + #endif + } +} diff --git a/ios/TurboDevice+headphone.swift b/ios/TurboDevice+headphone.swift new file mode 100644 index 0000000..dafd2d8 --- /dev/null +++ b/ios/TurboDevice+headphone.swift @@ -0,0 +1,33 @@ +import Foundation +import AVFoundation + +extension TurboDevice { + @objc + func isHeadphonesConnected(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + let currentRoute = AVAudioSession.sharedInstance().currentRoute + for desc in currentRoute.outputs { + let portType = desc.portType + if portType == .headphones || portType == .bluetoothA2DP || portType == .bluetoothHFP { + resolve(true) + } + } + resolve(false) + } + + @objc + func headphoneConnectionDidChange() { + let isConnected = { + let currentRoute = AVAudioSession.sharedInstance().currentRoute + for desc in currentRoute.outputs { + let portType = desc.portType + if portType == .headphones || portType == .bluetoothA2DP || portType == .bluetoothHFP { + return true + } + } + return false + }() + sendEvent(withName: "TurboDevice_headphoneConnectionDidChange", body: [isConnected]) + } + +} diff --git a/ios/TurboDevice+localAuthentication.swift b/ios/TurboDevice+localAuthentication.swift new file mode 100644 index 0000000..53a8fb1 --- /dev/null +++ b/ios/TurboDevice+localAuthentication.swift @@ -0,0 +1,16 @@ +import Foundation +#if !os(tvOS) +import LocalAuthentication +#endif + +extension TurboDevice { + private func isPinOrFingerprintSet(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if os(tvOS) + resolve(false) +#else + let evaluated = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) + resolve(evaluated) +#endif + } +} diff --git a/ios/TurboDevice+location.swift b/ios/TurboDevice+location.swift new file mode 100644 index 0000000..48cbe2d --- /dev/null +++ b/ios/TurboDevice+location.swift @@ -0,0 +1,35 @@ +import Foundation +import CoreLocation + +extension TurboDevice { + @objc + func isLocationEnabled(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + let enabled = CLLocationManager.locationServicesEnabled() + resolve(enabled) + } + + @objc + func getAvailableLocationProviders(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if !os(tvOS) + let locationServicesEnabled = CLLocationManager.locationServicesEnabled() + let significantLocationChangeMonitoringAvailable = CLLocationManager.significantLocationChangeMonitoringAvailable() + let headingAvailable = CLLocationManager.headingAvailable() + let isRangingAvailable = CLLocationManager.isRangingAvailable() + let providers = [ + "locationServicesEnabled": locationServicesEnabled, + "significantLocationChangeMonitoringAvailable": significantLocationChangeMonitoringAvailable, + "headingAvailable": headingAvailable, + "isRangingAvailable": isRangingAvailable, + ] + resolve(providers) +#else + let locationServicesEnabled = isLocationEnabled() + let providers = [ + "locationServicesEnabled": locationServicesEnabled, + ] + resolve(providers) +#endif + } +} diff --git a/ios/TurboDevice+network.swift b/ios/TurboDevice+network.swift new file mode 100644 index 0000000..b3bafc3 --- /dev/null +++ b/ios/TurboDevice+network.swift @@ -0,0 +1,52 @@ +import Foundation +import CoreTelephony + +extension TurboDevice { + func getCarrier(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if os(tvOS) || targetEnvironment(macCatalyst) + resolve("unknown") +#else + let netInfo = CTTelephonyNetworkInfo() + if #available(iOS 12.0, *) { + resolve(netInfo.serviceSubscriberCellularProviders?.first?.value.carrierName ?? "unknown") + } else { + resolve(netInfo.subscriberCellularProvider?.carrierName ?? "unknown") + } +#endif + } + + // copy from https://stackoverflow.com/a/73853838 + func getIpAddress(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + var address : String? + + var ifaddr : UnsafeMutablePointer? + guard getifaddrs(&ifaddr) == 0 else { return reject("getIpAddress failed", nil, nil) } + guard let firstAddr = ifaddr else { return reject("getIpAddress failed", nil, nil) } + + for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { + let interface = ifptr.pointee + + let addrFamily = interface.ifa_addr.pointee.sa_family + if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { + + // Check interface name: + // wifi = ["en0"] + // wired = ["en2", "en3", "en4"] + // cellular = ["pdp_ip0","pdp_ip1","pdp_ip2","pdp_ip3"] + let name = String(cString: interface.ifa_name) + if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" { + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), + &hostname, socklen_t(hostname.count), + nil, socklen_t(0), NI_NUMERICHOST) + address = String(cString: hostname) + } + } + } + freeifaddrs(ifaddr) + + resolve(address) + } +} diff --git a/ios/TurboDevice+storage.swift b/ios/TurboDevice+storage.swift new file mode 100644 index 0000000..934ebb1 --- /dev/null +++ b/ios/TurboDevice+storage.swift @@ -0,0 +1,57 @@ +import Foundation +// MARK: - disk +extension TurboDevice { + @objc + func getTotalDiskCapacity(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let storage = getStorage() else { return reject("get total disk capacity failed", nil, nil) } + let fileSystemSize = storage[.systemSize] as? Int ?? 0 + let totalSpace = UInt64(fileSystemSize) + resolve(Double(totalSpace)) + } + + @objc + private func getFreeDiskStorage(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let storage = getStorage() else { return reject("get total disk capacity failed", nil, nil) } + let fileSystemSize = storage[.systemFreeSize] as? Int ?? 0 + let freeSpace = UInt64(fileSystemSize) + resolve(Double(freeSpace)) + } + + private func getStorage() -> [FileAttributeKey : Any]? { + let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) + do { + let storage = try FileManager.default.attributesOfFileSystem(forPath: paths.last!) + return storage + } catch { + return nil + } + } +} + +// MARK: - memory +extension TurboDevice { + @objc + private func getTotalMemory(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + let totalMemory = ProcessInfo.processInfo.physicalMemory + resolve(Double(totalMemory)) + } + + @objc + private func getUsedMemory(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + var taskInfo = mach_task_basic_info() + var count = mach_msg_type_number_t(MemoryLayout.size)/4 + let kerr: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) + } + } + if kerr != KERN_SUCCESS { + reject("get used memory failed", nil, nil) + } + resolve(UInt64(taskInfo.resident_size)) + } +} diff --git a/ios/TurboDevice+typography.swift b/ios/TurboDevice+typography.swift new file mode 100644 index 0000000..cefe4e7 --- /dev/null +++ b/ios/TurboDevice+typography.swift @@ -0,0 +1,40 @@ +import Foundation + +extension TurboDevice { + @objc + func getFontScale(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + let contentSize = UIScreen.main.traitCollection.preferredContentSizeCategory + let fontScale = { + switch contentSize { + case .extraSmall: + return 0.82 + case .small: + return 0.88 + case .medium: + return 0.95 + case .large: + return 1.0 + case .extraLarge: + return 1.12 + case .extraExtraLarge: + return 1.23 + case .extraExtraExtraLarge: + return 1.35 + case .accessibilityMedium: + return 1.64 + case .accessibilityLarge: + return 1.95 + case .accessibilityExtraLarge: + return 2.35 + case .accessibilityExtraExtraLarge: + return 2.76 + case .accessibilityExtraExtraExtraLarge: + return 3.12 + default: + return 1.0 + } + }() + resolve(fontScale) + } +} diff --git a/ios/TurboDevice+web.swift b/ios/TurboDevice+web.swift new file mode 100644 index 0000000..c64d07f --- /dev/null +++ b/ios/TurboDevice+web.swift @@ -0,0 +1,10 @@ +import Foundation + +extension TurboDevice { + @objc + func getUserAgent(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + let userAgent = WKWebView().value(forKey: "userAgent") as! String + resolve(userAgent) + } +} diff --git a/ios/TurboDevice-Bridging-Header.h b/ios/TurboDevice-Bridging-Header.h index dea7ff6..3518469 100644 --- a/ios/TurboDevice-Bridging-Header.h +++ b/ios/TurboDevice-Bridging-Header.h @@ -1,2 +1,6 @@ #import #import +#import +#import +#import + diff --git a/ios/TurboDevice.mm b/ios/TurboDevice.mm index 45dcea9..c401a93 100644 --- a/ios/TurboDevice.mm +++ b/ios/TurboDevice.mm @@ -3,12 +3,81 @@ @interface RCT_EXTERN_MODULE(TurboDevice, NSObject) RCT_EXTERN_METHOD(multiply:(float)a withB:(float)b - withResolver:(RCTPromiseResolveBlock)resolve - withRejecter:(RCTPromiseRejectBlock)reject) + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + (BOOL)requiresMainQueueSetup { return NO; } +# pragma mark storage + +RCT_EXTERN_METHOD(getTotalDiskCapacity:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getFreeDiskStorage:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getTotalMemory:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getUsedMemory:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark battery + +RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark network + +RCT_EXTERN_METHOD(getCarrier:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getIpAddress:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark location + +RCT_EXTERN_METHOD(isLocationEnabled:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getAvailableLocationProviders:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark headphone + +RCT_EXTERN_METHOD(isHeadphonesConnected:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark brightness + +RCT_EXTERN_METHOD(getBrightness:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark typography + +RCT_EXTERN_METHOD(getFontScale:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark web + +RCT_EXTERN_METHOD(getUserAgent:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark arch + +RCT_EXTERN_METHOD(getSupportedAbis:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark localAuthentication + +RCT_EXTERN_METHOD(isPinOrFingerprintSet:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +# pragma mark others + +RCT_EXTERN_METHOD(isEmulator:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getFirstInstallTime:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + @end diff --git a/ios/TurboDevice.swift b/ios/TurboDevice.swift index 407362d..22e24a9 100644 --- a/ios/TurboDevice.swift +++ b/ios/TurboDevice.swift @@ -1,8 +1,104 @@ -@objc(TurboDevice) -class TurboDevice: NSObject { +import AVFoundation +@objc(TurboDevice) +class TurboDevice: RCTEventEmitter { + + var hasListeners: Bool? +#if !os(tvOS) + let kLowBatteryThreshold: Float = 0.2 +#endif + + override func supportedEvents() -> [String]! { + return [ + "TurboDevice_batteryLevelDidChange", + "TurboDevice_batteryLevelIsLow", + "TurboDevice_powerStateDidChange", + "TurboDevice_headphoneConnectionDidChange", + "TurboDevice_brightnessDidChange" + ]; + } + + override func constantsToExport() -> [AnyHashable : Any]! { + return [ + "deviceId": UIDevice.identifer, + "model": UIDevice.modelName, + "bundleId": getBundleId(), + "systemName": getSystemName(), + "systemVersion": getSystemVersion(), + "appVersion": getAppVersion(), + "buildNumber": getBuildNumber(), + "isTablet": isTablet(), + "appName": getAppName(), + "brand": "Apple", + "deviceType": getDeviceTypeName(), + "isDisplayZoomed": isDisplayZoomed(), + ]; + } + + override init() { + super.init() +#if !os(tvOS) + UIDevice.current.isBatteryMonitoringEnabled = true + NotificationCenter.default.addObserver(self, + selector: #selector(batteryLevelDidChange), + name: UIDevice.batteryLevelDidChangeNotification, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(powerStateDidChange), + name: UIDevice.batteryLevelDidChangeNotification, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(powerStateDidChange), + name: Notification.Name.NSProcessInfoPowerStateDidChange, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(headphoneConnectionDidChange), + name: AVAudioSession.routeChangeNotification, + object: AVAudioSession.sharedInstance()) + NotificationCenter.default.addObserver(self, + selector: #selector(brightnessDidChange), + name: UIScreen.brightnessDidChangeNotification, + object: nil) +#endif + } + @objc(multiply:withB:withResolver:withRejecter:) func multiply(a: Float, b: Float, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void { resolve(a*b) } } + +// MARK: - isEmulator +extension TurboDevice { + + @objc + func isEmulator(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { +#if targetEnvironment(simulator) + resolve(true) +#else + resolve(false) +#endif + } +} + +// MARK: - getFirstInstallTime +extension TurboDevice { + @objc + func getFirstInstallTime(_ resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let path = FileManager.default.urls(for: + .documentDirectory,in: + .userDomainMask).last + else { return reject("getFirstInstallTime", nil, nil) } + var installDate: Date? + do { + let attributesOfItem = try FileManager.default.attributesOfItem(atPath: path.absoluteString) + installDate = attributesOfItem[.creationDate] as? Date + } catch { + reject("getFirstInstallTime", nil, nil) + } + resolve(Int64(installDate!.timeIntervalSince1970 * 1000)) + } +} + diff --git a/ios/UIDevice+utils.swift b/ios/UIDevice+utils.swift new file mode 100644 index 0000000..ecbafff --- /dev/null +++ b/ios/UIDevice+utils.swift @@ -0,0 +1,111 @@ +import UIKit + +public extension UIDevice { + static let identifer: String = { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + return identifier + }() + static let modelName: String = { + func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity +#if os(iOS) + switch identifier { + case "iPod5,1": return "iPod touch (5th generation)" + case "iPod7,1": return "iPod touch (6th generation)" + case "iPod9,1": return "iPod touch (7th generation)" + case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" + case "iPhone4,1": return "iPhone 4s" + case "iPhone5,1", "iPhone5,2": return "iPhone 5" + case "iPhone5,3", "iPhone5,4": return "iPhone 5c" + case "iPhone6,1", "iPhone6,2": return "iPhone 5s" + case "iPhone7,2": return "iPhone 6" + case "iPhone7,1": return "iPhone 6 Plus" + case "iPhone8,1": return "iPhone 6s" + case "iPhone8,2": return "iPhone 6s Plus" + case "iPhone9,1", "iPhone9,3": return "iPhone 7" + case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" + case "iPhone10,1", "iPhone10,4": return "iPhone 8" + case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" + case "iPhone10,3", "iPhone10,6": return "iPhone X" + case "iPhone11,2": return "iPhone XS" + case "iPhone11,4", "iPhone11,6": return "iPhone XS Max" + case "iPhone11,8": return "iPhone XR" + case "iPhone12,1": return "iPhone 11" + case "iPhone12,3": return "iPhone 11 Pro" + case "iPhone12,5": return "iPhone 11 Pro Max" + case "iPhone13,1": return "iPhone 12 mini" + case "iPhone13,2": return "iPhone 12" + case "iPhone13,3": return "iPhone 12 Pro" + case "iPhone13,4": return "iPhone 12 Pro Max" + case "iPhone14,4": return "iPhone 13 mini" + case "iPhone14,5": return "iPhone 13" + case "iPhone14,2": return "iPhone 13 Pro" + case "iPhone14,3": return "iPhone 13 Pro Max" + case "iPhone14,7": return "iPhone 14" + case "iPhone14,8": return "iPhone 14 Plus" + case "iPhone15,2": return "iPhone 14 Pro" + case "iPhone15,3": return "iPhone 14 Pro Max" + case "iPhone15,4": return "iPhone 15" + case "iPhone15,5": return "iPhone 15 Plus" + case "iPhone16,1": return "iPhone 15 Pro" + case "iPhone16,2": return "iPhone 15 Pro Max" + case "iPhone8,4": return "iPhone SE" + case "iPhone12,8": return "iPhone SE (2nd generation)" + case "iPhone14,6": return "iPhone SE (3rd generation)" + case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2" + case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)" + case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)" + case "iPad6,11", "iPad6,12": return "iPad (5th generation)" + case "iPad7,5", "iPad7,6": return "iPad (6th generation)" + case "iPad7,11", "iPad7,12": return "iPad (7th generation)" + case "iPad11,6", "iPad11,7": return "iPad (8th generation)" + case "iPad12,1", "iPad12,2": return "iPad (9th generation)" + case "iPad13,18", "iPad13,19": return "iPad (10th generation)" + case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" + case "iPad5,3", "iPad5,4": return "iPad Air 2" + case "iPad11,3", "iPad11,4": return "iPad Air (3rd generation)" + case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)" + case "iPad13,16", "iPad13,17": return "iPad Air (5th generation)" + case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad mini" + case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad mini 2" + case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad mini 3" + case "iPad5,1", "iPad5,2": return "iPad mini 4" + case "iPad11,1", "iPad11,2": return "iPad mini (5th generation)" + case "iPad14,1", "iPad14,2": return "iPad mini (6th generation)" + case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)" + case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)" + case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": return "iPad Pro (11-inch) (1st generation)" + case "iPad8,9", "iPad8,10": return "iPad Pro (11-inch) (2nd generation)" + case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro (11-inch) (3rd generation)" + case "iPad14,3", "iPad14,4": return "iPad Pro (11-inch) (4th generation)" + case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch) (1st generation)" + case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)" + case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return "iPad Pro (12.9-inch) (3rd generation)" + case "iPad8,11", "iPad8,12": return "iPad Pro (12.9-inch) (4th generation)" + case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":return "iPad Pro (12.9-inch) (5th generation)" + case "iPad14,5", "iPad14,6": return "iPad Pro (12.9-inch) (6th generation)" + case "AppleTV5,3": return "Apple TV" + case "AppleTV6,2": return "Apple TV 4K" + case "AudioAccessory1,1": return "HomePod" + case "AudioAccessory5,1": return "HomePod mini" + case "i386", "x86_64", "arm64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" + default: return identifier + } +#elseif os(tvOS) + switch identifier { + case "AppleTV5,3": return "Apple TV 4" + case "AppleTV6,2": return "Apple TV 4K" + case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" + default: return identifier + } +#endif + } + let deviceName = mapToDevice(identifier: UIDevice.identifer) + return deviceName + }() +} diff --git a/src/index.tsx b/src/index.tsx index 7188abd..bcb778a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,3 +20,75 @@ const TurboDevice = NativeModules.TurboDevice export function multiply(a: number, b: number): Promise { return TurboDevice.multiply(a, b); } + +export function getTotalDiskCapacity(): Promise { + return TurboDevice.getTotalDiskCapacity(); +} + +export function getFreeDiskStorage(): Promise { + return TurboDevice.getFreeDiskStorage(); +} + +export function getTotalMemory(): Promise { + return TurboDevice.getTotalMemory(); +} + +export function getUsedMemory(): Promise { + return TurboDevice.getUsedMemory(); +} + +export function getBatteryLevel(): Promise { + return TurboDevice.getBatteryLevel(); +} + +export function isBatteryCharging(): Promise { + return TurboDevice.isBatteryCharging(); +} + +export function getCarrier(): Promise { + return TurboDevice.getCarrier(); +} + +export function getIpAddress(): Promise { + return TurboDevice.getIpAddress(); +} + +export function isLocationEnabled(): Promise { + return TurboDevice.isLocationEnabled(); +} + +export function getAvailableLocationProviders(): Promise> { + return TurboDevice.getAvailableLocationProviders(); +} + +export function isHeadphonesConnected(): Promise { + return TurboDevice.isHeadphonesConnected(); +} + +export function getBrightness(): Promise { + return TurboDevice.getBrightness(); +} + +export function getFontScale(): Promise { + return TurboDevice.getFontScale(); +} + +export function getUserAgent(): Promise { + return TurboDevice.getUserAgent(); +} + +export function getSupportedAbis(): Promise { + return TurboDevice.getSupportedAbis(); +} + +export function isPinOrFingerprintSet(): Promise { + return TurboDevice.isPinOrFingerprintSet(); +} + +export function isEmulator(): Promise { + return TurboDevice.isEmulator(); +} + +export function getFirstInstallTime(): Promise { + return TurboDevice.getFirstInstallTime(); +}