diff --git a/CHANGELOG.md b/CHANGELOG.md index 03602d0c..a78e942e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New features + +- Add `Device.hasActionButton` and `Device.allDevicesWithActionButton` on iOS, including simulator detection, with support generated from GYB device parameters. ([#499](https://github.com/devicekit/DeviceKit/pull/499)) Thanks to [Simon Mau (@simonxmau)](https://github.com/simonxmau) for the original contribution! + ### Maintenance - Simplify GYB device definitions with named feature parameters and defaults for unsupported features, preserving the generated Swift output. ([#508](https://github.com/devicekit/DeviceKit/pull/508)) diff --git a/README.md b/README.md index 10abba0f..aa09c96e 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ See our detailed [changelog](CHANGELOG.md) for the latest features, improvements - [x] Detect available sensors (Touch ID, Face ID) - [x] Detect available disk space - [x] Apple Pencil support detection +- [x] Action Button detection ## Requirements @@ -169,6 +170,14 @@ if device.isSimulator { } ``` +### Check if a device has an Action Button +```swift +let device = Device.current +if device.hasActionButton { + // Action Button specific behavior +} +``` + ### Get the Simulator Device ```swift let device = Device.current diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index bd5a0638..13e7908b 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -1265,6 +1265,16 @@ public enum Device { return isOneOf(Device.allDevicesWithDynamicIsland) || isOneOf(Device.allDevicesWithDynamicIsland.map(Device.simulator)) } + /// All devices that have an Action Button. + public static var allDevicesWithActionButton: [Device] { + return [.iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e] + } + + /// Returns whether or not the device has an Action Button. + public var hasActionButton: Bool { + return isOneOf(Device.allDevicesWithActionButton) || isOneOf(Device.allDevicesWithActionButton.map(Device.simulator)) + } + /// All devices that have 3D Touch support. public static var allDevicesWith3dTouchSupport: [Device] { return [.iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax] diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index 7a61e94a..b38fd17b 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -24,6 +24,7 @@ class Device: supportsWirelessCharging=False, hasRoundedDisplayCorners=False, hasDynamicIsland=False, + hasActionButton=False, applePencilSupport=0, hasForce3dTouchSupport=False, cameras=0, @@ -50,6 +51,7 @@ class Device: self.supportsWirelessCharging = supportsWirelessCharging self.hasRoundedDisplayCorners = hasRoundedDisplayCorners self.hasDynamicIsland = hasDynamicIsland + self.hasActionButton = hasActionButton self.applePencilSupport = applePencilSupport self.hasForce3dTouchSupport = hasForce3dTouchSupport self.cameras = cameras @@ -184,54 +186,66 @@ iPhones = [ Device("iPhone15Pro", "Device is an [iPhone 15 Pro](https://support.apple.com/en-us/111829)", "", ["iPhone16,1"], 6.1, (9, 19.5), "iPhone 15 Pro", "iPhone 15 Pro", 460, cpu="a17Pro", isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True), + cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone15ProMax", "Device is an [iPhone 15 Pro Max](https://support.apple.com/en-us/111828)", "", ["iPhone16,2"], 6.7, (9, 19.5), "iPhone 15 Pro Max", "iPhone 15 Pro Max", 460, cpu="a17Pro", isPlusFormFactor=True, isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, - has5gSupport=True), + has5gSupport=True, + hasActionButton=True), Device("iPhone16", "Device is an [iPhone 16]()", "", ["iPhone17,3"], 6.1, (9, 19.5), "iPhone 16", "iPhone 16", 460, cpu="a18", isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=13, hasUSBCConnectivity=True, has5gSupport=True), + cameras=13, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone16Plus", "Device is an [iPhone 16 Plus]()", "", ["iPhone17,4"], 6.7, (9, 19.5), "iPhone 16 Plus", "iPhone 16 Plus", 460, cpu="a18", isPlusFormFactor=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=13, hasUSBCConnectivity=True, has5gSupport=True), + cameras=13, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone16Pro", "Device is an [iPhone 16 Pro]()", "", ["iPhone17,1"], 6.3, (9, 19.5), "iPhone 16 Pro", "iPhone 16 Pro", 460, cpu="a18Pro", isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True), + cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone16ProMax", "Device is an [iPhone 16 Pro Max]()", "", ["iPhone17,2"], 6.9, (9, 19.5), "iPhone 16 Pro Max", "iPhone 16 Pro Max", 460, cpu="a18Pro", isPlusFormFactor=True, isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, - has5gSupport=True), + has5gSupport=True, + hasActionButton=True), Device("iPhone16e", "Device is an [iPhone 16e](https://support.apple.com/en-us/122208)", "https://cdsassets.apple.com/live/7WUAS350/images/tech-specs/122208-iphone-16e.png", ["iPhone17,5"], 6.1, (9, 19.5), "iPhone 16e", "iPhone 16e", 460, cpu="a18", isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, cameras=1, - hasUSBCConnectivity=True, has5gSupport=True), + hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone17", "Device is an [iPhone 17]()", "", ["iPhone18,3"], 6.3, (9, 19.5), "iPhone 17", "iPhone 17", 460, cpu="a19", isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=13, hasUSBCConnectivity=True, has5gSupport=True), + cameras=13, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone17Pro", "Device is an [iPhone 17 Pro]()", "", ["iPhone18,1"], 6.3, (9, 19.5), "iPhone 17 Pro", "iPhone 17 Pro", 460, cpu="a19Pro", isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True), + cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone17ProMax", "Device is an [iPhone 17 Pro Max]()", "", ["iPhone18,2"], 6.9, (9, 19.5), "iPhone 17 Pro Max", "iPhone 17 Pro Max", 460, cpu="a19Pro", isPlusFormFactor=True, isPro=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, - has5gSupport=True), + has5gSupport=True, + hasActionButton=True), Device("iPhoneAir", "Device is an [iPhone Air]()", "", ["iPhone18,4"], 6.5, (9, 19.5), "iPhone Air", "iPhone Air", 460, cpu="a19Pro", isPlusFormFactor=True, isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, - cameras=1, hasUSBCConnectivity=True, has5gSupport=True), + cameras=1, hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), Device("iPhone17e", "Device is an [iPhone 17e]()", "", ["iPhone18,5"], 6.1, (9, 19.5), "iPhone 17e", "iPhone 17e", 460, cpu="a19", isXSeries=True, hasFaceID=True, hasSensorHousing=True, supportsWirelessCharging=True, hasRoundedDisplayCorners=True, cameras=1, - hasUSBCConnectivity=True, has5gSupport=True), + hasUSBCConnectivity=True, has5gSupport=True, + hasActionButton=True), ] iPads = [ @@ -969,6 +983,16 @@ public enum Device { return isOneOf(Device.allDevicesWithDynamicIsland) || isOneOf(Device.allDevicesWithDynamicIsland.map(Device.simulator)) } + /// All devices that have an Action Button. + public static var allDevicesWithActionButton: [Device] { + return [${', '.join(list(map(lambda device: "." + device.caseName, list(filter(lambda device: device.hasActionButton == True, iOSDevices)))))}] + } + + /// Returns whether or not the device has an Action Button. + public var hasActionButton: Bool { + return isOneOf(Device.allDevicesWithActionButton) || isOneOf(Device.allDevicesWithActionButton.map(Device.simulator)) + } + /// All devices that have 3D Touch support. public static var allDevicesWith3dTouchSupport: [Device] { return [${', '.join(list(map(lambda device: "." + device.caseName, list(filter(lambda device: device.hasForce3dTouchSupport == True, iOSDevices)))))}] diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 1e87af88..45219a17 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -551,6 +551,31 @@ class DeviceKitTests: XCTestCase { } } + func testHasActionButton() { + let actionButtonDevices: [Device] = [ + .iPhone15Pro, + .iPhone15ProMax, + .iPhone16, + .iPhone16Plus, + .iPhone16Pro, + .iPhone16ProMax, + .iPhone16e, + .iPhone17, + .iPhone17Pro, + .iPhone17ProMax, + .iPhoneAir, + .iPhone17e, + ] + XCTAssertEqual(Device.allDevicesWithActionButton, actionButtonDevices) + for device in Device.allRealDevices { + let expected = device.isOneOf(actionButtonDevices) + XCTAssertEqual(device.hasActionButton, expected, "Action Button support for \(device)") + XCTAssertEqual(Device.simulator(device).hasActionButton, expected, "Simulator Action Button support for \(device)") + } + XCTAssertFalse(Device.unknown("unrecognized-device").hasActionButton) + XCTAssertFalse(Device.simulator(.unknown("unrecognized-device")).hasActionButton) + } + func testHas5gSupport() { let has5gDevices: [Device] = [ .iPhone12,