refactor oscprofilemanager to swift
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
.DS_Store
|
||||
Build
|
||||
DerivedData
|
||||
build-home/
|
||||
*.xccheckout
|
||||
.github
|
||||
README.md
|
||||
@@ -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 = "<group>"; };
|
||||
84351B062C29ACCA00A77989 /* OnScreenButtonState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OnScreenButtonState.h; sourceTree = "<group>"; };
|
||||
84351B072C29ACCA00A77989 /* LayoutOnScreenControls.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LayoutOnScreenControls.m; sourceTree = "<group>"; };
|
||||
84351B082C29ACCA00A77989 /* OSCProfilesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCProfilesManager.h; sourceTree = "<group>"; };
|
||||
84351B092C29ACCA00A77989 /* OnScreenButtonState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnScreenButtonState.m; sourceTree = "<group>"; };
|
||||
84351B0A2C29ACCA00A77989 /* OSCProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCProfile.m; sourceTree = "<group>"; };
|
||||
84351B0B2C29ACCB00A77989 /* OSCProfilesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCProfilesManager.m; sourceTree = "<group>"; };
|
||||
A1D4E0A22FBBBBCC00112233 /* OSCProfilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSCProfilesManager.swift; sourceTree = "<group>"; };
|
||||
84351B0C2C29ACCB00A77989 /* LayoutOnScreenControls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LayoutOnScreenControls.h; sourceTree = "<group>"; };
|
||||
84351B0D2C29ACCB00A77989 /* OSCProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCProfile.h; sourceTree = "<group>"; };
|
||||
84351B292C29AD6000A77989 /* ToolBarContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolBarContainerView.m; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#import "LayoutOnScreenControls.h"
|
||||
#import "OnScreenButtonState.h"
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
|
||||
|
||||
@@ -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,6 +180,7 @@ static CGRect layoutViewBounds;
|
||||
if(!importPencilProProfile && [GenericUtils isIPad]){
|
||||
[profilesEncoded removeObjectAtIndex:1];
|
||||
}
|
||||
*/
|
||||
|
||||
if(targetProfiles.count > 0) [targetProfiles removeObjectAtIndex:0];
|
||||
NSMutableArray* localEncodedPofiles = [self encodedProfilesFromArray:targetProfiles];
|
||||
@@ -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];
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#endif
|
||||
#import "OnScreenButtonState.h"
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
#import "DataManager.h"
|
||||
|
||||
#define UPDATE_BUTTON(x, y) (buttonFlags = \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#import "VoidLink-Swift.h"
|
||||
#import "Connection.h"
|
||||
#import "Plot.h"
|
||||
#import "OSCProfilesManager.h"
|
||||
|
||||
#import <UIKit/UIGestureRecognizerSubclass.h>
|
||||
|
||||
|
||||
@@ -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 <sys/socket.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"
|
||||
|
||||
Reference in New Issue
Block a user