From 384724ff2799907ab7c626a568b58792758e94ab Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Fri, 5 Jun 2026 21:48:57 +0800 Subject: [PATCH] refactor oscprofilemanager to swift --- .gitignore | 3 +- VoidLink.xcodeproj/project.pbxproj | 10 +- .../Input/CustomOSC/LayoutOnScreenControls.m | 1 - VoidLink/Input/CustomOSC/OSCProfilesManager.m | 16 +- .../Input/CustomOSC/OSCProfilesManager.swift | 543 ++++++++++++++++++ VoidLink/Input/OnScreenControls.m | 2 +- VoidLink/Input/StreamView.m | 1 - VoidLink/Localization/Localizable.xcstrings | 23 + VoidLink/Utility/IAPManager.swift | 5 +- .../LayoutOnScreenControlsViewController.m | 1 - .../ViewControllers/SettingsViewController.m | 1 - .../StreamFrameViewController.m | 2 - VoidLink/VoidLink-Bridging-Header.h | 1 - 13 files changed, 592 insertions(+), 17 deletions(-) create mode 100644 VoidLink/Input/CustomOSC/OSCProfilesManager.swift diff --git a/.gitignore b/.gitignore index bca4bde6..df87f781 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .DS_Store Build DerivedData +build-home/ *.xccheckout .github -README.md \ No newline at end of file +README.md diff --git a/VoidLink.xcodeproj/project.pbxproj b/VoidLink.xcodeproj/project.pbxproj index 380dfbc6..5b490ffe 100644 --- a/VoidLink.xcodeproj/project.pbxproj +++ b/VoidLink.xcodeproj/project.pbxproj @@ -47,7 +47,7 @@ 84351B0E2C29ACCB00A77989 /* LayoutOnScreenControls.m in Sources */ = {isa = PBXBuildFile; fileRef = 84351B072C29ACCA00A77989 /* LayoutOnScreenControls.m */; }; 84351B0F2C29ACCB00A77989 /* OnScreenButtonState.m in Sources */ = {isa = PBXBuildFile; fileRef = 84351B092C29ACCA00A77989 /* OnScreenButtonState.m */; }; 84351B102C29ACCB00A77989 /* OSCProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 84351B0A2C29ACCA00A77989 /* OSCProfile.m */; }; - 84351B112C29ACCB00A77989 /* OSCProfilesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 84351B0B2C29ACCB00A77989 /* OSCProfilesManager.m */; }; + 84351B112C29ACCB00A77989 /* OSCProfilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D4E0A22FBBBBCC00112233 /* OSCProfilesManager.swift */; }; 84351B2D2C29AD6100A77989 /* ToolBarContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84351B292C29AD6000A77989 /* ToolBarContainerView.m */; }; 84351B5D2C29ADBE00A77989 /* ProfileTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84351B5B2C29ADBE00A77989 /* ProfileTableViewCell.xib */; }; 843667F02D9D7173004A7D7F /* widgetTemplates.bin in Resources */ = {isa = PBXBuildFile; fileRef = 843667EF2D9D7173004A7D7F /* widgetTemplates.bin */; }; @@ -216,10 +216,9 @@ 84203F222C5F24C00032EEED /* OnScreenWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnScreenWidgetView.swift; sourceTree = ""; }; 84351B062C29ACCA00A77989 /* OnScreenButtonState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OnScreenButtonState.h; sourceTree = ""; }; 84351B072C29ACCA00A77989 /* LayoutOnScreenControls.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LayoutOnScreenControls.m; sourceTree = ""; }; - 84351B082C29ACCA00A77989 /* OSCProfilesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCProfilesManager.h; sourceTree = ""; }; 84351B092C29ACCA00A77989 /* OnScreenButtonState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnScreenButtonState.m; sourceTree = ""; }; 84351B0A2C29ACCA00A77989 /* OSCProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCProfile.m; sourceTree = ""; }; - 84351B0B2C29ACCB00A77989 /* OSCProfilesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCProfilesManager.m; sourceTree = ""; }; + A1D4E0A22FBBBBCC00112233 /* OSCProfilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSCProfilesManager.swift; sourceTree = ""; }; 84351B0C2C29ACCB00A77989 /* LayoutOnScreenControls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LayoutOnScreenControls.h; sourceTree = ""; }; 84351B0D2C29ACCB00A77989 /* OSCProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCProfile.h; sourceTree = ""; }; 84351B292C29AD6000A77989 /* ToolBarContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolBarContainerView.m; sourceTree = ""; }; @@ -492,8 +491,7 @@ 84351B092C29ACCA00A77989 /* OnScreenButtonState.m */, 84351B0D2C29ACCB00A77989 /* OSCProfile.h */, 84351B0A2C29ACCA00A77989 /* OSCProfile.m */, - 84351B082C29ACCA00A77989 /* OSCProfilesManager.h */, - 84351B0B2C29ACCB00A77989 /* OSCProfilesManager.m */, + A1D4E0A22FBBBBCC00112233 /* OSCProfilesManager.swift */, ); path = CustomOSC; sourceTree = ""; @@ -1159,7 +1157,7 @@ FB53E1431BE5DC4400CD6ECE /* IdManager.m in Sources */, 189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */, 9897B6A1221260EF00966419 /* VoidController.m in Sources */, - 84351B112C29ACCB00A77989 /* OSCProfilesManager.m in Sources */, + 84351B112C29ACCB00A77989 /* OSCProfilesManager.swift in Sources */, FB89462919F646E200339C8A /* mkcert.c in Sources */, FB9AFD281A7C84ED00872C98 /* HttpResponse.m in Sources */, FB89463019F646E200339C8A /* StreamConfiguration.m in Sources */, diff --git a/VoidLink/Input/CustomOSC/LayoutOnScreenControls.m b/VoidLink/Input/CustomOSC/LayoutOnScreenControls.m index 84435e93..4084b397 100644 --- a/VoidLink/Input/CustomOSC/LayoutOnScreenControls.m +++ b/VoidLink/Input/CustomOSC/LayoutOnScreenControls.m @@ -11,7 +11,6 @@ #import "LayoutOnScreenControls.h" #import "OnScreenButtonState.h" -#import "OSCProfilesManager.h" #import "VoidLink-Swift.h" diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.m b/VoidLink/Input/CustomOSC/OSCProfilesManager.m index 97feeee5..78c43fad 100644 --- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m +++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m @@ -171,6 +171,7 @@ static CGRect layoutViewBounds; if(profile && targetProfiles.count > 1) [targetProfiles removeObject:profile]; */ + /* NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; bool importPencilProProfile = ([bundleId isEqualToString:@"com.voidlink.iOS"] || [bundleId isEqualToString:@"com.voidlinkextreme.iOS"] @@ -179,7 +180,8 @@ static CGRect layoutViewBounds; if(!importPencilProProfile && [GenericUtils isIPad]){ [profilesEncoded removeObjectAtIndex:1]; } - + */ + if(targetProfiles.count > 0) [targetProfiles removeObjectAtIndex:0]; NSMutableArray* localEncodedPofiles = [self encodedProfilesFromArray:targetProfiles]; [profilesEncoded addObjectsFromArray:localEncodedPofiles]; @@ -281,6 +283,18 @@ static CGRect layoutViewBounds; return profilesDecoded; } +- (NSMutableArray *) decodeProfilesFrom:(NSMutableArray* )profilesEncoded { + NSSet *classes = [NSSet setWithObjects:[NSString class], [NSMutableData class], [NSMutableArray class], [OSCProfile class], [OnScreenButtonState class], nil]; + NSMutableArray *profilesDecoded = [[NSMutableArray alloc] init]; + OSCProfile *profileDecoded; + for (NSData *profileEncoded in profilesEncoded) { + + profileDecoded = [NSKeyedUnarchiver unarchivedObjectOfClasses: classes fromData:profileEncoded error: nil]; + [profilesDecoded addObject: profileDecoded]; + } + return profilesDecoded; +} + - (OSCProfile *) getSelectedProfile { // NSLog(@"getAllProfiles test %f", CACurrentMediaTime()); NSMutableArray *profiles = [self getAllProfiles]; diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.swift b/VoidLink/Input/CustomOSC/OSCProfilesManager.swift new file mode 100644 index 00000000..d749e97d --- /dev/null +++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.swift @@ -0,0 +1,543 @@ +// +// OSCProfilesManager.m -> OSCProfilesManager.swift +// Moonlight +// +// Created by Long Le on 1/1/23. +// Copyright © 2023 Moonlight Game Streaming Project. All rights reserved. +// +// Modified by True砖家 since 2024.6.24 +// Copyright © 2024 True砖家 @ Bilibili. All rights reserved. +// + +import UIKit + +@objcMembers +class OSCProfilesManager: NSObject { + private static let profilesDefaultsKey = "OSCProfiles" + private static let widgetProfileUpdatedKey = "widgetProfileUpdated-20260605" + + private static var sharedInstance: OSCProfilesManager? + private static var onScreenWidgetViews: NSMutableSet? + private static var layoutViewBounds: CGRect = .zero + + var currentProfiles: NSMutableArray = [] + var widgetSizeTransition: WidgetSizeTransition = .keepWidgetSize + + @objc(sharedManager:) + class func sharedManager(_ viewBounds: CGRect) -> OSCProfilesManager { + if viewBounds != .zero { + layoutViewBounds = viewBounds + } + if let sharedInstance { + return sharedInstance + } + + let manager = OSCProfilesManager() + sharedInstance = manager + return manager + } + + @objc(setOnScreenWidgetViewsSet:) + class func setOnScreenWidgetViewsSet(_ set: NSMutableSet) { + onScreenWidgetViews = set + } + + @objc(setLayoutViewBounds:) + class func setLayoutViewBounds(_ bounds: CGRect) { + layoutViewBounds = bounds + } + + private static let profileCodingClasses: [AnyClass] = [ + NSString.self, + NSMutableData.self, + NSMutableArray.self, + OSCProfile.self, + OnScreenButtonState.self, + ] + + private static let buttonStateCodingClasses: [AnyClass] = [ + NSString.self, + OnScreenButtonState.self, + NSSet.self, + NSNumber.self, + ] + + private func oscProfile(withName name: String) -> OSCProfile? { + let profiles = getAllProfiles() + for case let profile as OSCProfile in profiles where profile.name == name { + return profile + } + return nil + } + + private func encodedProfiles(from profiles: NSMutableArray) -> NSMutableArray { + let profilesEncoded = NSMutableArray() + for case let profile as OSCProfile in profiles { + if let profileEncoded = try? NSKeyedArchiver.archivedData(withRootObject: profile, requiringSecureCoding: true) { + profilesEncoded.add(profileEncoded) + } + } + return profilesEncoded + } + + private func persistProfiles(_ profiles: NSMutableArray) { + let profilesEncoded = encodedProfiles(from: profiles) + persistEncodedProfiles(profilesEncoded) + } + + private func persistEncodedProfiles(_ profilesEncoded: NSMutableArray) { + guard let data = try? NSKeyedArchiver.archivedData(withRootObject: profilesEncoded, requiringSecureCoding: true) else { + return + } + let defaults = UserDefaults.standard + defaults.set(data, forKey: Self.profilesDefaultsKey) + defaults.synchronize() + } + + func deleteCurrentSelectedProfile() { + let profiles = getAllProfiles() + var selectedIndex = getIndexOfSelectedProfile() + + if selectedIndex != 0 { + profiles.removeObject(at: selectedIndex) + } else { + NSLog("Default profile!") + } + + selectedIndex -= 1 + if selectedIndex < 0 { + selectedIndex = 0 + } + + if let newSelectedProfile = profiles[safe: selectedIndex] as? OSCProfile { + newSelectedProfile.isSelected = true + } + + persistProfiles(profiles) + } + + func replaceSelectedProfile(with newProfile: OSCProfile, overwriteDefault: Bool) { + let profiles = getAllProfiles() + var index = 0 + + for case let profile as OSCProfile in profiles where profile.isSelected { + index = profiles.index(of: profile) + } + + if index > 0 || overwriteDefault { + profiles.replaceObject(at: index, with: newProfile) + } + + persistProfiles(profiles) + } + + @objc(replaceProfile:withProfile:) + func replaceProfile(_ oldProfile: OSCProfile, withProfile newProfile: OSCProfile) { + let profiles = getAllProfiles() + + for case let profile as OSCProfile in profiles { + profile.isSelected = false + } + + newProfile.isSelected = true + + var index = 0 + for i in 0 ..< profiles.count { + guard let profile = profiles[i] as? OSCProfile else { continue } + if profile.name == oldProfile.name { + index = i + } + } + + profiles.removeObject(at: index) + profiles.insert(newProfile, at: index) + persistProfiles(profiles) + } + + func replace(_ oldProfile: OSCProfile, with newProfile: OSCProfile) { + replaceProfile(oldProfile, withProfile: newProfile) + } + + func getEncodedProfiles() -> NSMutableArray { + guard + let profilesArrayEncoded = UserDefaults.standard.object(forKey: Self.profilesDefaultsKey) as? Data, + let profilesEncoded = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.profileCodingClasses, from: profilesArrayEncoded) as? NSMutableArray + else { + return NSMutableArray() + } + + return profilesEncoded + } + + func importEncodedProfiles(_ profilesEncoded: NSMutableArray) { + let localProfiles = currentProfiles.mutableCopy() as? NSMutableArray ?? NSMutableArray() + + if localProfiles.count > 0 { + localProfiles.removeObject(at: 0) + } + + let profilesDecoded = decodeProfiles(from: profilesEncoded) + + let localProfileNames = Set( + localProfiles.compactMap { ($0 as? OSCProfile)?.name } + ) + + for case let profile as OSCProfile in profilesDecoded { + if localProfileNames.contains(profile.name) { + profile.name = "\(profile.name)-\(LocalizationHelper.localizedString(forKey:"Restored"))" + } + } + + let importedProfileNames = Set( + profilesDecoded.compactMap { ($0 as? OSCProfile)?.name } + ) + + var localProfilesToRemove: [OSCProfile] = [] + for case let profile as OSCProfile in localProfiles { + if importedProfileNames.contains(profile.name) {localProfilesToRemove.append(profile)} + } + + for case let profile as Any in localProfilesToRemove { + localProfiles.remove(profile) + } + + for profile in localProfiles { + profilesDecoded.add(profile) + } + + persistEncodedProfiles(encodedProfiles(from: profilesDecoded)) + } + + private func isIPhone() -> Bool { + UIDevice.current.userInterfaceIdiom == .phone + } + + func importDefaultTemplates() { + guard let filePath = Bundle.main.path(forResource: isIPhone() ? "widgetTemplatesIPhone" : "widgetTemplates", ofType: "bin") else { + return + } + + do { + let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedIfSafe) + if let profilesEncoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSMutableData.self, NSMutableArray.self], from: fileData) as? NSMutableArray { + importEncodedProfiles(profilesEncoded) + setProfileToSelected(0) + } + } catch { + return + } + } + + func updateDefaultTemplates() { + guard let filePath = Bundle.main.path(forResource: isIPhone() ? "widgetTemplatesIPhone" : "widgetTemplates", ofType: "bin") else { + return + } + + do { + let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedIfSafe) + guard let defaultProfilesEncoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.profileCodingClasses, from: fileData) as? NSMutableArray else { + return + } + + let targetProfilesDecoded = getAllProfiles() + var newPencilProProfile: OSCProfile? + var pencilProProfileExisted = false + + for case let profileEncoded as Data in defaultProfilesEncoded { + guard let defaultProfileDecoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.profileCodingClasses, from: profileEncoded) as? OSCProfile else { + continue + } + + let targetProfile = findProfile(byName: defaultProfileDecoded.name, inProfileArray: targetProfilesDecoded) + if defaultProfileDecoded.name == "Pencil Pro" { + newPencilProProfile = defaultProfileDecoded.mutableCopy() as? OSCProfile + } + + if let targetProfile { + if targetProfile.name == "Pencil Pro" { + pencilProProfileExisted = true + } + let targetIndex = targetProfilesDecoded.index(of: targetProfile) + targetProfilesDecoded.replaceObject(at: targetIndex, with: defaultProfileDecoded) + } + } + + if let newPencilProProfile, !pencilProProfileExisted { + targetProfilesDecoded.insert(newPencilProProfile, at: 1) + } + + persistProfiles(targetProfilesDecoded) + setProfileToSelected(0) + } catch { + return + } + } + + func getAllProfiles() -> NSMutableArray { + guard + let profilesArrayEncoded = UserDefaults.standard.object(forKey: Self.profilesDefaultsKey) as? Data, + let profilesEncoded = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.profileCodingClasses, from: profilesArrayEncoded) as? NSMutableArray + else { + currentProfiles = NSMutableArray() + return currentProfiles + } + + let profilesDecoded = decodeProfiles(from: profilesEncoded) + currentProfiles = profilesDecoded + return profilesDecoded + } + + private func decodeProfiles(from profilesEncoded: NSMutableArray) -> NSMutableArray { + let profilesDecoded = NSMutableArray() + for case let profileEncoded as Data in profilesEncoded { + guard let profileDecoded = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.profileCodingClasses, from: profileEncoded) as? OSCProfile else { + continue + } + profilesDecoded.add(profileDecoded) + } + return profilesDecoded + } + + func getSelectedProfile() -> OSCProfile { + var profiles = getAllProfiles() + let defaults = UserDefaults.standard + let needImportDefaultTemplates = defaults.object(forKey: Self.widgetProfileUpdatedKey) == nil + + if profiles.count == 0 || needImportDefaultTemplates { + if profiles.count == 0 { + importDefaultTemplates() + } else { + updateDefaultTemplates() + } + defaults.set(true, forKey: Self.widgetProfileUpdatedKey) + defaults.synchronize() + profiles = getAllProfiles() + } + + for case let profile as OSCProfile in profiles where profile.isSelected { + return profile + } + + return (profiles.firstObject as? OSCProfile) ?? OSCProfile(name: "", buttonStates: NSMutableArray(), isSelected: false) + } + + func getIndexOfSelectedProfile() -> Int { + let profiles = getAllProfiles() + for case let profile as OSCProfile in profiles where profile.isSelected { + return profiles.index(of: profile) + } + return 0 + } + + func getIndexOfLastProfile() -> UInt32 { + let profiles = getAllProfiles() + return profiles.count > 0 ? UInt32(profiles.count - 1) : 0 + } + + func setProfileToSelected(_ tableIndex: UInt32) { + let profiles = getAllProfiles() + + for (idx, element) in profiles.enumerated() { + guard let profile = element as? OSCProfile else { continue } + profile.isSelected = idx == Int(tableIndex) + } + + persistProfiles(profiles) + } + + func updateSelectedProfile(_ oscButtonLayers: NSMutableSet) -> Bool { + let buttonStatesEncoded = convertOnScreenControllerAndWidgetsToButtonStates(oscButtonLayers) + if getIndexOfSelectedProfile() == 0 { + return false + } + + let selectedProfile = getSelectedProfile() + selectedProfile.buttonStatesEncoded = buttonStatesEncoded + selectedProfile.unfoldedExclusiveFolderSequence = OnScreenWidgetView.unfoldedExclusiveFolderSequence + selectedProfile.postExclusiveUnfoldedSequences = Set(OnScreenWidgetView.postExclusiveUnfoldedSequences.map { NSNumber(value: $0) }) + replaceSelectedProfile(with: selectedProfile, overwriteDefault: false) + return true + } + + func duplicateSelectedProfile(withName name: String) { + if profileNameAlreadyExist(name) { + return + } + + let profiles = getAllProfiles() + var newProfile: OSCProfile? + + for case let profile as OSCProfile in profiles where profile.isSelected { + profile.isSelected = false + newProfile = profile.mutableCopy() as? OSCProfile + newProfile?.isSelected = true + newProfile?.name = name + } + + guard let newProfile, let newProfileEncoded = try? NSKeyedArchiver.archivedData(withRootObject: newProfile, requiringSecureCoding: true) else { + return + } + + let profilesEncoded = encodedProfiles(from: profiles) + profilesEncoded.add(newProfileEncoded) + persistEncodedProfiles(profilesEncoded) + } + + func normalizeWidgetPosition(_ position: CGPoint) -> CGPoint { + var normalizedPosition = position + let originalPosition = position + if position.x > 1.0, position.y > 1.0 { + normalizedPosition.x = position.x / Self.layoutViewBounds.size.width + normalizedPosition.y = position.y / Self.layoutViewBounds.size.height + } + NSLog("layoutToolView bounds: %f, %f", Self.layoutViewBounds.size.width, Self.layoutViewBounds.size.height) + NSLog("position: %f, %f, denormalized position: %f, %f", normalizedPosition.x, normalizedPosition.y, originalPosition.x, originalPosition.y) + return normalizedPosition + } + + private func denormalizeWidgetPosition(_ position: CGPoint) -> CGPoint { + var denormalizedPosition = position + if position.x < 1.0, position.y < 1.0 { + denormalizedPosition.x = position.x * Self.layoutViewBounds.size.width + denormalizedPosition.y = position.y * Self.layoutViewBounds.size.height + } + return denormalizedPosition + } + + private func convertOnScreenControllerAndWidgetsToButtonStates(_ oscButtonLayers: NSMutableSet) -> NSMutableArray { + let buttonStatesEncoded = NSMutableArray() + + for case let oscButtonLayer as CALayer in oscButtonLayers { + if oscButtonLayer.isHidden { + continue + } + + let normalizedPosition = normalizeWidgetPosition(oscButtonLayer.position) + let layerName = oscButtonLayer.name ?? "" + let buttonState = OnScreenButtonState(buttonName: layerName, buttonType: 0, andPosition: normalizedPosition) + buttonState.isHidden = oscButtonLayer.isHidden + buttonState.oscLayerSizeFactor = OnScreenControls.getControllerLayerSizeFactor(oscButtonLayer) + buttonState.backgroundAlpha = CGFloat(oscButtonLayer.opacity) + + if let style = OnScreenControls.layerVibrationStyleDic()?.object(forKey: layerName) as? NSNumber { + buttonState.vibrationStyle = style.uint8Value + } else { + buttonState.vibrationStyle = UInt8(UIImpactFeedbackGenerator.FeedbackStyle.light.rawValue) + } + + if let buttonStateEncoded = try? NSKeyedArchiver.archivedData(withRootObject: buttonState, requiringSecureCoding: true) { + buttonStatesEncoded.add(buttonStateEncoded) + } + } + + widgetSizeTransition = .keepWidgetSize + for widgetView in OnScreenWidgetView.mapping.values { + let normalizedPosition = normalizeWidgetPosition(widgetView.storedCenter) + let buttonState = OnScreenButtonState(buttonName: widgetView.cmdString, buttonType: 1, andPosition: normalizedPosition) + buttonState.alias = widgetView.widgetLabel + buttonState.sequence = widgetView.sequence + buttonState.sequenceSet = Set(widgetView.sequenceSet.map { NSNumber(value: $0) }) + buttonState.parentSequence = widgetView.parentSequence + buttonState.autoDockTimer = Int16(widgetView.autoDockIdleDuration) + buttonState.dockedAlpha = widgetView.autoDockSettledAlpha + buttonState.folded = widgetView.folded + buttonState.revealMode = UInt8(widgetView.revealMode.rawValue) + buttonState.bulkMoveEnabled = widgetView.bulkMoveEnabled + buttonState.widthFactor = normalizeSizeWidthFactor(with: widgetView) + buttonState.heightFactor = normalizeSizeHeightFactor(with: widgetView) + buttonState.backgroundAlpha = widgetView.originalBackgroundAlpha + buttonState.labelAlpha = widgetView.originalLabelAlpha + buttonState.borderAlpha = widgetView.borderAlpha + buttonState.highlightAlpha = widgetView.highlightAlpha + buttonState.borderWidth = widgetView.borderWidth + buttonState.highlightSizeFactor = widgetView.highlightSizeFactor + buttonState.autoTapInterval = UInt16(widgetView.autoTapInterval) + buttonState.autoTapRepeats = widgetView.autoTapRepeats + buttonState.vibrationStyle = UInt8(widgetView.vibrationStyle) + buttonState.mouseButtonAction = UInt8(widgetView.mouseButtonAction.rawValue) + buttonState.animatesTransition = widgetView.animatesTransition + buttonState.sensitivityFactorY = widgetView.sensitivityFactorY + buttonState.slideThreshold = widgetView.slideThreshold + buttonState.yawFactor = widgetView.yawFactor + buttonState.pitchFactor = widgetView.pitchFactor + buttonState.rollFactor = widgetView.rollFactor + buttonState.decelerationRateX = widgetView.decelerationRateX + buttonState.decelerationRateY = widgetView.decelerationRateY + buttonState.widgetShape = widgetView.shape + buttonState.walkModeThreshold = widgetView.dWheelWalkModeThreshold + buttonState.minStickOffset = widgetView.minStickOffset + buttonState.buttonMode = UInt8(widgetView.buttonMode.rawValue) + buttonState.sprintKeyActionType = widgetView.sprintKeyActionType.rawValue + buttonState.sprintKeyThreshold = widgetView.sprintKeyThreshold + buttonState.walkKeyActionType = widgetView.walkKeyActionType.rawValue + buttonState.walkKeyThreshold = widgetView.walkKeyThreshold + buttonState.sensitivityFactorX = widgetView.sensitivityFactorX + buttonState.componentSizeFactor = normalizeComponentSizeFactor(with: widgetView) + buttonState.touchPointAnchored = widgetView.touchPointAnchored + buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset + + if let buttonStateEncoded = try? NSKeyedArchiver.archivedData(withRootObject: buttonState, requiringSecureCoding: true) { + buttonStatesEncoded.add(buttonStateEncoded) + } + } + + return buttonStatesEncoded + } + + private func getReferenceLen() -> CGFloat { + let screenBounds = UIScreen.main.bounds + let screenWidthInPoints = screenBounds.width + let screenHeightInPoints = screenBounds.height + let longSideLen = max(screenWidthInPoints, screenHeightInPoints) + + if widgetSizeTransition == .transitionWithOrientation { + return screenWidthInPoints + } + return longSideLen + } + + private func normalizeSizeWidthFactor(with widget: OnScreenWidgetView) -> CGFloat { + widget.bounds.size.width / getReferenceLen() * 10000 + } + + private func normalizeSizeHeightFactor(with widget: OnScreenWidgetView) -> CGFloat { + widget.bounds.size.height / getReferenceLen() * 10000 + } + + private func normalizeComponentSizeFactor(with widget: OnScreenWidgetView) -> CGFloat { + if widget.isStickWheel || widget.isDisplacementBasedStickPad { + return widget.denormalizedComponentSizeFactor * widget.baselineDiameter / getReferenceLen() * 10000 + } + return 1 + } + + func profileNameAlreadyExist(_ name: String) -> Bool { + let profiles = getAllProfiles() + for case let profile as OSCProfile in profiles where profile.name == name { + return true + } + return false + } + + @objc(findProfileByName:inProfileArray:) + func findProfile(byName name: String, inProfileArray profiles: NSMutableArray) -> OSCProfile? { + for case let profile as OSCProfile in profiles where profile.name == name { + return profile + } + return nil + } + + func unarchiveButtonStateEncoded(_ data: Data) -> OnScreenButtonState { + if let buttonState = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: Self.buttonStateCodingClasses, from: data) as? OnScreenButtonState { + return buttonState + } + return OnScreenButtonState(buttonName: "", buttonType: 0, andPosition: .zero) + } +} + +private extension NSMutableArray { + subscript(safe index: Int) -> Any? { + guard index >= 0, index < count else { return nil } + return self[index] + } +} diff --git a/VoidLink/Input/OnScreenControls.m b/VoidLink/Input/OnScreenControls.m index bfcb7bf2..9a924e5c 100644 --- a/VoidLink/Input/OnScreenControls.m +++ b/VoidLink/Input/OnScreenControls.m @@ -18,7 +18,7 @@ #import #endif #import "OnScreenButtonState.h" -#import "OSCProfilesManager.h" +#import "VoidLink-Swift.h" #import "DataManager.h" #define UPDATE_BUTTON(x, y) (buttonFlags = \ diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index 427f0ba4..f61e2c5a 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -17,7 +17,6 @@ #import "ControllerSupport.h" #import "KeyboardSupport.h" #import "VoidLink-Swift.h" -#import "OSCProfilesManager.h" #import "NativeTouchPointer.h" #import "NativeTouchHandler.h" #import "PureNativeTouchHandler.h" diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 2ba50c18..47b21077 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -13199,6 +13199,29 @@ } } }, + "Restored" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已恢复" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "已恢復" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "已恢復" + } + } + } + }, "Resume App" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/Utility/IAPManager.swift b/VoidLink/Utility/IAPManager.swift index 85ae2ee6..e855622e 100644 --- a/VoidLink/Utility/IAPManager.swift +++ b/VoidLink/Utility/IAPManager.swift @@ -48,7 +48,7 @@ import StoreKit case (.PencilProPack, "com.voidlink.tf.debug10.iOS"): return "com.pencilpro.voidlink.debug.iOS" case (.PencilProPack, "com.voidlink.tf.iOS"): - return "com.pencilpro.voidlink.tf.iOS" + return "" default: return "" } @@ -371,6 +371,9 @@ import StoreKit @objc static public func inAppPurchaseAction(viewController: UIViewController, product: AddOnProduct){ + + + let alert = UIAlertController(title: product.productName(), message: LocalizationHelper.localizedString(forKey: "No purchase found", product.productName()), preferredStyle: .alert) diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 3d472b50..22dbf233 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -12,7 +12,6 @@ #import "LayoutOnScreenControlsViewController.h" #import "OnScreenButtonState.h" #import "OnScreenControls.h" -#import "OSCProfilesManager.h" #import "LocalizationHelper.h" #import "VoidLink-Swift.h" // #import "ThemeManager.h" diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 87454ec9..63aa7d6b 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -15,7 +15,6 @@ #import "VoidLink-Swift.h" #import "Connection.h" #import "Plot.h" -#import "OSCProfilesManager.h" #import diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index b617dc0a..0f6b03d6 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -24,8 +24,6 @@ #import "CustomTapGestureRecognizer.h" #import "LocalizationHelper.h" #import "VoidLink-Swift.h" -#import "OSCProfilesManager.h" -#import "VoidLink-Swift.h" #import "NativeTouchPointer.h" #include diff --git a/VoidLink/VoidLink-Bridging-Header.h b/VoidLink/VoidLink-Bridging-Header.h index d8202fd2..5e1353ed 100644 --- a/VoidLink/VoidLink-Bridging-Header.h +++ b/VoidLink/VoidLink-Bridging-Header.h @@ -8,7 +8,6 @@ #import "DataManager.h" #import "LocalizationHelper.h" #import "OSCProfile.h" -#import "OSCProfilesManager.h" #import "LayoutOnScreenControls.h" #import "ToolBarContainerView.h" #import "opus.h"