From 32d7756d8c21abdea4700ec9064bae860e67b5c2 Mon Sep 17 00:00:00 2001 From: DoncicX <348031375@qq.com> Date: Tue, 24 Feb 2026 13:40:35 +0800 Subject: [PATCH] iOS: extract device/platform info into DeviceInfoHelper, keep Settings platform string as iOS X.Y.Z --- .../ios/Sources/Device/DeviceInfoHelper.swift | 71 +++++++++++++++++++ .../Sources/Device/DeviceStatusService.swift | 17 ++--- .../Gateway/GatewayConnectionController.swift | 46 ++---------- apps/ios/Sources/Settings/SettingsTab.swift | 32 +-------- apps/ios/SwiftSources.input.xcfilelist | 3 + 5 files changed, 87 insertions(+), 82 deletions(-) create mode 100644 apps/ios/Sources/Device/DeviceInfoHelper.swift diff --git a/apps/ios/Sources/Device/DeviceInfoHelper.swift b/apps/ios/Sources/Device/DeviceInfoHelper.swift new file mode 100644 index 00000000000..eeed54c4652 --- /dev/null +++ b/apps/ios/Sources/Device/DeviceInfoHelper.swift @@ -0,0 +1,71 @@ +import Foundation +import UIKit + +import Darwin + +/// Shared device and platform info for Settings, gateway node payloads, and device status. +enum DeviceInfoHelper { + /// e.g. "iOS 18.0.0" or "iPadOS 18.0.0" by interface idiom. Use for gateway/device payloads. + static func platformString() -> String { + let v = ProcessInfo.processInfo.operatingSystemVersion + let name = switch UIDevice.current.userInterfaceIdiom { + case .pad: + "iPadOS" + case .phone: + "iOS" + default: + "iOS" + } + return "\(name) \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)" + } + + /// Always "iOS X.Y.Z" for UI display (e.g. Settings), matching legacy behavior on iPad. + static func platformStringForDisplay() -> String { + let v = ProcessInfo.processInfo.operatingSystemVersion + return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)" + } + + /// Device family for display: "iPad", "iPhone", or "iOS". + static func deviceFamily() -> String { + switch UIDevice.current.userInterfaceIdiom { + case .pad: + "iPad" + case .phone: + "iPhone" + default: + "iOS" + } + } + + /// Machine model identifier from uname (e.g. "iPhone17,1"). + static func modelIdentifier() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let machine = withUnsafeBytes(of: &systemInfo.machine) { ptr in + String(bytes: ptr.prefix { $0 != 0 }, encoding: .utf8) + } + let trimmed = machine?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return trimmed.isEmpty ? "unknown" : trimmed + } + + /// App marketing version only, e.g. "2026.2.0" or "dev". + static func appVersion() -> String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev" + } + + /// App build string, e.g. "123" or "". + static func appBuild() -> String { + let raw = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" + return raw.trimmingCharacters(in: .whitespacesAndNewlines) + } + + /// Display string for Settings: "1.2.3" or "1.2.3 (456)" when build differs. + static func openClawVersionString() -> String { + let version = appVersion() + let build = appBuild() + if build.isEmpty || build == version { + return version + } + return "\(version) (\(build))" + } +} diff --git a/apps/ios/Sources/Device/DeviceStatusService.swift b/apps/ios/Sources/Device/DeviceStatusService.swift index fed2716b5b8..a80a98101fa 100644 --- a/apps/ios/Sources/Device/DeviceStatusService.swift +++ b/apps/ios/Sources/Device/DeviceStatusService.swift @@ -26,12 +26,12 @@ final class DeviceStatusService: DeviceStatusServicing { func info() -> OpenClawDeviceInfoPayload { let device = UIDevice.current - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev" - let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "0" + let appVersion = DeviceInfoHelper.appVersion() + let appBuild = DeviceStatusService.fallbackAppBuild(DeviceInfoHelper.appBuild()) let locale = Locale.preferredLanguages.first ?? Locale.current.identifier return OpenClawDeviceInfoPayload( deviceName: device.name, - modelIdentifier: Self.modelIdentifier(), + modelIdentifier: DeviceInfoHelper.modelIdentifier(), systemName: device.systemName, systemVersion: device.systemVersion, appVersion: appVersion, @@ -75,13 +75,8 @@ final class DeviceStatusService: DeviceStatusServicing { return OpenClawStorageStatusPayload(totalBytes: total, freeBytes: free, usedBytes: used) } - private static func modelIdentifier() -> String { - var systemInfo = utsname() - uname(&systemInfo) - let machine = withUnsafeBytes(of: &systemInfo.machine) { ptr in - String(bytes: ptr.prefix { $0 != 0 }, encoding: .utf8) - } - let trimmed = machine?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - return trimmed.isEmpty ? "unknown" : trimmed + /// Fallback for payloads that require a non-empty build (e.g. "0"). + private static func fallbackAppBuild(_ build: String) -> String { + build.isEmpty ? "0" : build } } diff --git a/apps/ios/Sources/Gateway/GatewayConnectionController.swift b/apps/ios/Sources/Gateway/GatewayConnectionController.swift index 2b7f94ba453..a770fcb2c6f 100644 --- a/apps/ios/Sources/Gateway/GatewayConnectionController.swift +++ b/apps/ios/Sources/Gateway/GatewayConnectionController.swift @@ -921,44 +921,6 @@ final class GatewayConnectionController { private static func motionAvailable() -> Bool { CMMotionActivityManager.isActivityAvailable() || CMPedometer.isStepCountingAvailable() } - - private func platformString() -> String { - let v = ProcessInfo.processInfo.operatingSystemVersion - let name = switch UIDevice.current.userInterfaceIdiom { - case .pad: - "iPadOS" - case .phone: - "iOS" - default: - "iOS" - } - return "\(name) \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)" - } - - private func deviceFamily() -> String { - switch UIDevice.current.userInterfaceIdiom { - case .pad: - "iPad" - case .phone: - "iPhone" - default: - "iOS" - } - } - - private func modelIdentifier() -> String { - var systemInfo = utsname() - uname(&systemInfo) - let machine = withUnsafeBytes(of: &systemInfo.machine) { ptr in - String(bytes: ptr.prefix { $0 != 0 }, encoding: .utf8) - } - let trimmed = machine?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - return trimmed.isEmpty ? "unknown" : trimmed - } - - private func appVersion() -> String { - Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev" - } } #if DEBUG @@ -980,19 +942,19 @@ extension GatewayConnectionController { } func _test_platformString() -> String { - self.platformString() + DeviceInfoHelper.platformString() } func _test_deviceFamily() -> String { - self.deviceFamily() + DeviceInfoHelper.deviceFamily() } func _test_modelIdentifier() -> String { - self.modelIdentifier() + DeviceInfoHelper.modelIdentifier() } func _test_appVersion() -> String { - self.appVersion() + DeviceInfoHelper.appVersion() } func _test_setGateways(_ gateways: [GatewayDiscoveryModel.DiscoveredGateway]) { diff --git a/apps/ios/Sources/Settings/SettingsTab.swift b/apps/ios/Sources/Settings/SettingsTab.swift index 024a4cbf42b..3ff2ed465c3 100644 --- a/apps/ios/Sources/Settings/SettingsTab.swift +++ b/apps/ios/Sources/Settings/SettingsTab.swift @@ -374,9 +374,9 @@ struct SettingsTab: View { .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) - LabeledContent("Device", value: self.deviceFamily()) - LabeledContent("Platform", value: self.platformString()) - LabeledContent("OpenClaw", value: self.openClawVersionString()) + LabeledContent("Device", value: DeviceInfoHelper.deviceFamily()) + LabeledContent("Platform", value: DeviceInfoHelper.platformStringForDisplay()) + LabeledContent("OpenClaw", value: DeviceInfoHelper.openClawVersionString()) } } } @@ -584,32 +584,6 @@ struct SettingsTab: View { return trimmed.isEmpty ? "Not connected" : trimmed } - private func platformString() -> String { - let v = ProcessInfo.processInfo.operatingSystemVersion - return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)" - } - - private func deviceFamily() -> String { - switch UIDevice.current.userInterfaceIdiom { - case .pad: - "iPad" - case .phone: - "iPhone" - default: - "iOS" - } - } - - private func openClawVersionString() -> String { - let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev" - let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" - let trimmedBuild = build.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmedBuild.isEmpty || trimmedBuild == version { - return version - } - return "\(version) (\(trimmedBuild))" - } - private func featureToggle( _ title: String, isOn: Binding, diff --git a/apps/ios/SwiftSources.input.xcfilelist b/apps/ios/SwiftSources.input.xcfilelist index 5b1ba7d70e6..514ca732673 100644 --- a/apps/ios/SwiftSources.input.xcfilelist +++ b/apps/ios/SwiftSources.input.xcfilelist @@ -4,6 +4,9 @@ Sources/Gateway/GatewayDiscoveryModel.swift Sources/Gateway/GatewaySettingsStore.swift Sources/Gateway/KeychainStore.swift Sources/Camera/CameraController.swift +Sources/Device/DeviceInfoHelper.swift +Sources/Device/DeviceStatusService.swift +Sources/Device/NetworkStatusService.swift Sources/Chat/ChatSheet.swift Sources/Chat/IOSGatewayChatTransport.swift Sources/OpenClawApp.swift