Commit 3349dd14 authored by shenyong's avatar shenyong

首页数据获取

parent 21848fa1
...@@ -46,6 +46,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -46,6 +46,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// 初始化广告SDK // 初始化广告SDK
AdvManager.shared.initAdertisementSDK() AdvManager.shared.initAdertisementSDK()
PhotoManager.shared.config()
return true return true
} }
...@@ -79,22 +81,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -79,22 +81,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
/// 首次进来开始请求首页数据 /// 首次进来开始请求首页数据
func findHomeData(){ func findHomeData(){
PhotoAndVideoMananger.getPrivacy {[weak self] status in // PhotoAndVideoMananger.getPrivacy {[weak self] status in
guard let self else {return} // guard let self else {return}
if let photoStatus = status as? PrivacyType { // if let photoStatus = status as? PrivacyType {
Singleton.shared.photoPermission = photoStatus // Singleton.shared.photoPermission = photoStatus
if photoStatus == .authorized { // if photoStatus == .authorized {
// 有授权加载数据 // // 有授权加载数据
PhotoAndVideoMananger.mananger.setAssets() // PhotoAndVideoMananger.mananger.setAssets()
// 读取缓存数据 // // 读取缓存数据
readCacheModel() // readCacheModel()
}else{ // }else{
// 没有授权,更新删除缓存数据 // // 没有授权,更新删除缓存数据
PhotoDataManager.manager.loadDataFromPhotos { model in} // PhotoDataManager.manager.loadDataFromPhotos { model in}
Print("未获取授权") // Print("未获取授权")
} // }
} // }
} // }
} }
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -133,7 +133,7 @@ extension IAPManager { ...@@ -133,7 +133,7 @@ extension IAPManager {
switch result { switch result {
case .success(let receipt): case .success(let receipt):
// 打印完整收据信息,方便调试 // 打印完整收据信息,方便调试
print("收据信息:\(receipt)") //print("收据信息:\(receipt)")
let status = self.checkSubscriptionStatus(receiptInfo: receipt) let status = self.checkSubscriptionStatus(receiptInfo: receipt)
DispatchQueue.main.async { DispatchQueue.main.async {
......
...@@ -79,7 +79,7 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -79,7 +79,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
return return
} }
let maxConcurrency = 4 // 最大并发数 let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, resolutionGroups.count / maxConcurrency) let batchSize = max(1, resolutionGroups.count / maxConcurrency)
for batchIndex in stride(from: 0, to: resolutionGroups.count, by: batchSize) { for batchIndex in stride(from: 0, to: resolutionGroups.count, by: batchSize) {
......
...@@ -12,36 +12,72 @@ import CoreML ...@@ -12,36 +12,72 @@ import CoreML
import Vision import Vision
import UIKit import UIKit
class PhotoManager:ObservableObject{ // 在 PhotoManager 类之前添加
actor ImageCacheActor {
private let cache = NSCache<NSString, UIImage>()
func getImage(for key: String) -> UIImage? {
cache.object(forKey: key as NSString)
}
func setImage(_ image: UIImage, for key: String) {
cache.setObject(image, forKey: key as NSString)
}
}
// 定义状态枚举
enum BaseDataLoadingState {
case notLoaded
case loading
case loaded
case failed(Error)
}
class PhotoManager{
static let shared = PhotoManager() static let shared = PhotoManager()
private let imageCache = ImageCacheActor()
// 定义存储数据结构
private struct StorageData: Codable {
var video: [AssetModel]
var other: [AssetModel]
var screenshot: [AssetModel]
}
// 获取本地存储路径
private func getLocalStoragePath() -> URL {
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return documentsPath.appendingPathComponent("assets_data.json")
}
// 保存数据到本地
private let fileQueue = DispatchQueue(label: "com.cleanphoto.fileoperations")
private init() { private init() {
requestAuthorization() requestAuthorization()
} }
func config(){}
// 添加 Published 属性 // 添加 Published 属性
@Published private(set) var baseDataLoadingState: BaseDataLoadingState = .notLoaded private(set) var baseDataLoadingState: BaseDataLoadingState = .notLoaded
// 定义状态枚举
enum BaseDataLoadingState {
case notLoaded
case loading
case loaded
case failed(Error)
}
var permissionStatus:PHAuthorizationStatus = .notDetermined
// MARK: - 基础配置 // MARK: - 基础配置
// 所有的媒体资源 // 所有的媒体资源
var allAssets:[PHAsset] = [] var allAssets:[PHAsset] = []
// 照片 // 照片
@Published private(set) var photosAssets:[PHAsset] = [] private(set) var photosAssets:[PHAsset] = []
// 截图 // 截图
@Published private(set) var screenShotAssets:[PHAsset] = [] private(set) var screenShotAssets:[PHAsset] = []
// 视频 // 视频
@Published private(set) var videoAssets:[PHAsset] = [] private(set) var videoAssets:[PHAsset] = []
// 其他 // 其他
@Published private(set) var otherAssets:[PHAsset] = [] private(set) var otherAssets:[PHAsset] = []
// 相似图片分组 // 相似图片分组
var similarModels:[[AssetModel]] = [] var similarModels:[[AssetModel]] = []
...@@ -57,20 +93,22 @@ class PhotoManager:ObservableObject{ ...@@ -57,20 +93,22 @@ class PhotoManager:ObservableObject{
// 视频 // 视频
var videoModels:[AssetModel] = [] var videoModels:[AssetModel] = []
// 其他 // 其他
var otherModels:[AssetModel] = [] var otherModels:[AssetModel] = []
@Published private(set) var screenShotTotalSize:Int64 = 0
@Published private(set) var videoTotalSize:Int64 = 0
@Published private(set) var otherTotalSize:Int64 = 0
private(set) var screenShotTotalSize:Int64 = 0
private(set) var videoTotalSize:Int64 = 0
private(set) var otherTotalSize:Int64 = 0
private var currentPage: Int = 0 private var currentPage: Int = 0
private let pageSize: Int = 50 // 每次加载的数量 private let pageSize: Int = 50 // 每次加载的数量
// MARK: -基础函数 // MARK: -基础函数
// 获取相册权限 // 获取相册权限
func requestAuthorization(completion: @escaping (Bool) -> Void) { func requestAuthorization(completion: @escaping (Bool) -> Void) {
PHPhotoLibrary.requestAuthorization { status in PHPhotoLibrary.requestAuthorization {[weak self] status in
guard let weakSelf = self else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
weakSelf.permissionStatus = status
completion(status == .authorized) completion(status == .authorized)
} }
} }
...@@ -90,8 +128,10 @@ class PhotoManager:ObservableObject{ ...@@ -90,8 +128,10 @@ class PhotoManager:ObservableObject{
baseDataLoadingState = .loading baseDataLoadingState = .loading
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {[weak self] in
guard let weakSelf = self else { return }
let fetchOptions = PHFetchOptions() let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
...@@ -99,6 +139,8 @@ class PhotoManager:ObservableObject{ ...@@ -99,6 +139,8 @@ class PhotoManager:ObservableObject{
let videoAllAssets = PHAsset.fetchAssets(with: .video, options: fetchOptions) let videoAllAssets = PHAsset.fetchAssets(with: .video, options: fetchOptions)
let fetchOptionsS = PHFetchOptions() let fetchOptionsS = PHFetchOptions()
fetchOptionsS.predicate = NSPredicate(format: "mediaSubtypes == %d", PHAssetMediaSubtype.photoScreenshot.rawValue) fetchOptionsS.predicate = NSPredicate(format: "mediaSubtypes == %d", PHAssetMediaSubtype.photoScreenshot.rawValue)
let screenShotAllAssets = PHAsset.fetchAssets(with: .image, options: fetchOptionsS) let screenShotAllAssets = PHAsset.fetchAssets(with: .image, options: fetchOptionsS)
...@@ -107,31 +149,48 @@ class PhotoManager:ObservableObject{ ...@@ -107,31 +149,48 @@ class PhotoManager:ObservableObject{
let videoAssetsArray = videoAllAssets.objects(at: IndexSet(0..<videoAllAssets.count)) let videoAssetsArray = videoAllAssets.objects(at: IndexSet(0..<videoAllAssets.count))
let screenShotArray = screenShotAllAssets.objects(at: IndexSet(0..<screenShotAllAssets.count)) let screenShotArray = screenShotAllAssets.objects(at: IndexSet(0..<screenShotAllAssets.count))
let otherArray = photoAssetsArray.filter {!screenShotArray.contains($0) } let otherArray = photoAssetsArray.filter {!screenShotArray.contains($0) }
weakSelf.allAssets = photoAssetsArray + videoAssetsArray
print("基本数据执行完毕") // print("基本数据执行完毕")
// 在主线程更新状态 // 在主线程更新状态
DispatchQueue.main.async { DispatchQueue.main.async {
self.photosAssets = photoAssetsArray weakSelf.photosAssets = photoAssetsArray
self.videoAssets = videoAssetsArray weakSelf.videoAssets = videoAssetsArray
self.screenShotAssets = screenShotArray weakSelf.screenShotAssets = screenShotArray
self.otherAssets = otherArray weakSelf.otherAssets = otherArray
self.baseDataLoadingState = .loaded weakSelf.baseDataLoadingState = .loaded
print("基本数据执行完毕")
NotificationCenter.default.post(name: .getBaseAssetsSuccess, object: nil)
} }
} }
} }
// 转化视频模型和获取大小 // 转化视频模型和获取大小
func convertVideoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){ func convertVideoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
Task{ Task{
// 先尝试从本地加载
if let localModels = loadFromLocal(type: "video") {
let videoTotalSize = Int64(localModels.reduce(0){$0+$1.assetSize})
await MainActor.run {
self.videoModels = localModels
self.videoTotalSize = videoTotalSize
complectionHandler?(localModels, videoTotalSize)
}
}
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2) self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2)
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒") print("视频转换总耗时: \(duration)秒")
let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize}) let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "video", models: self.videoModels)
await MainActor.run { await MainActor.run {
self.videoTotalSize = videoTotalSize self.videoTotalSize = videoTotalSize
complectionHandler?(self.videoModels,videoTotalSize) complectionHandler?(self.videoModels,videoTotalSize)
...@@ -142,11 +201,25 @@ class PhotoManager:ObservableObject{ ...@@ -142,11 +201,25 @@ class PhotoManager:ObservableObject{
// 转化其他图片模型和获取大小 // 转化其他图片模型和获取大小
func convertOtherPhotoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){ func convertOtherPhotoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
Task{ Task{
// 先尝试从本地加载
if let localModels = loadFromLocal(type: "other") {
let otherTotalSize = Int64(localModels.reduce(0){$0+$1.assetSize})
await MainActor.run {
self.otherModels = localModels
self.otherTotalSize = otherTotalSize
complectionHandler?(localModels, otherTotalSize)
}
}
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1) self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1)
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒") print("其他图片转换总耗时: \(duration)秒")
let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize}) let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "other", models: self.otherModels)
await MainActor.run { await MainActor.run {
self.otherTotalSize = otherTotalSize self.otherTotalSize = otherTotalSize
complectionHandler?(self.otherModels,otherTotalSize) complectionHandler?(self.otherModels,otherTotalSize)
...@@ -157,18 +230,99 @@ class PhotoManager:ObservableObject{ ...@@ -157,18 +230,99 @@ class PhotoManager:ObservableObject{
// 转化截图模型和获取大小 // 转化截图模型和获取大小
func convertScreenShotModels(complectionHandler:(([AssetModel],Int64) ->Void)?){ func convertScreenShotModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
Task{ Task{
// 先尝试从本地加载
if let localModels = loadFromLocal(type: "screenshot") {
let screenShotTotalSize = Int64(localModels.reduce(0){$0+$1.assetSize})
await MainActor.run {
self.screenShotModels = localModels
self.screenShotTotalSize = screenShotTotalSize
complectionHandler?(localModels, screenShotTotalSize)
}
return
}
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1) self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1)
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("截图转换总耗时: \(duration)秒") print("截图转换总耗时: \(duration)秒")
let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize}) let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "screenshot", models: self.screenShotModels)
await MainActor.run { await MainActor.run {
self.screenShotTotalSize = screenShotTotalSize self.screenShotTotalSize = screenShotTotalSize
complectionHandler?(self.otherModels,screenShotTotalSize) complectionHandler?(self.screenShotModels,screenShotTotalSize)
}
}
}
}
// MARK: - 本地存储
extension PhotoManager{
private func saveToLocal(type: String, models: [AssetModel]) {
fileQueue.async {
let filePath = self.getLocalStoragePath()
let encoder = JSONEncoder()
let decoder = JSONDecoder() // 添加这行
encoder.dateEncodingStrategy = .iso8601
decoder.dateDecodingStrategy = .iso8601 // 添加这行
// 在串行队列中执行读取-修改-写入操作
var storageData: StorageData
if let existingData = try? Data(contentsOf: filePath),
let decoded = try? decoder.decode(StorageData.self, from: existingData) {
storageData = decoded
} else {
storageData = StorageData(video: [], other: [], screenshot: [])
}
switch type {
case "video":
storageData.video = models
case "other":
storageData.other = models
case "screenshot":
storageData.screenshot = models
default:
break
}
do {
let data = try encoder.encode(storageData)
try data.write(to: filePath)
} catch {
print("保存本地数据失败:", error)
} }
} }
} }
// 从本地读取数据
private func loadFromLocal(type: String) -> [AssetModel]? {
return fileQueue.sync {
let filePath = getLocalStoragePath()
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
guard let data = try? Data(contentsOf: filePath),
let storageData = try? decoder.decode(StorageData.self, from: data) else {
return nil
}
switch type {
case "video":
return storageData.video
case "other":
return storageData.other
case "screenshot":
return storageData.screenshot
default:
return nil
}
}
}
} }
...@@ -216,33 +370,90 @@ extension PhotoManager{ ...@@ -216,33 +370,90 @@ extension PhotoManager{
} }
// 根据 localIdentifier 获取图片 // 根据 localIdentifier 获取图片
// func getImage(localIdentifier: String, targetSize: CGSize = PHImageManagerMaximumSize, completion: @escaping (UIImage?) -> Void) {
//
// // 根据 localIdentifier 获取 PHAsset
// let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
// guard let asset = fetchResult.firstObject else {
// completion(nil)
// return
// }
//
// // 配置图片请求选项
// let options = PHImageRequestOptions()
// options.version = .current
// options.deliveryMode = .highQualityFormat
// options.isNetworkAccessAllowed = true // 允许从 iCloud 下载
// options.resizeMode = .exact
//
// // 请求图片
// PHImageManager.default().requestImage(
// for: asset,
// targetSize: targetSize,
// contentMode: .aspectFit,
// options: options
// ) { image, info in
//
// DispatchQueue.main.async {
// completion(image)
// }
// }
// }
func getImage(localIdentifier: String, targetSize: CGSize = PHImageManagerMaximumSize, completion: @escaping (UIImage?) -> Void) { func getImage(localIdentifier: String, targetSize: CGSize = PHImageManagerMaximumSize, completion: @escaping (UIImage?) -> Void) {
// 根据 localIdentifier 获取 PHAsset Task {
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) // 先检查缓存
guard let asset = fetchResult.firstObject else { if let cachedImage = await imageCache.getImage(for: localIdentifier) {
completion(nil) await MainActor.run {
return completion(cachedImage)
} }
return
// 配置图片请求选项 }
let options = PHImageRequestOptions()
options.version = .current // 根据 localIdentifier 获取 PHAsset
options.deliveryMode = .highQualityFormat let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
options.isNetworkAccessAllowed = true // 允许从 iCloud 下载 guard let asset = fetchResult.firstObject else {
options.resizeMode = .exact await MainActor.run {
completion(nil)
// 请求图片 }
PHImageManager.default().requestImage( return
for: asset, }
targetSize: targetSize,
contentMode: .aspectFit, // 配置图片请求选项
options: options let options = PHImageRequestOptions()
) { image, info in options.version = .current
DispatchQueue.main.async { options.deliveryMode = .highQualityFormat
completion(image) options.isNetworkAccessAllowed = true
options.resizeMode = .exact
// 请求图片
PHImageManager.default().requestImage(
for: asset,
targetSize: targetSize,
contentMode: .aspectFit,
options: options
) { [weak self] image, info in
guard let self = self else { return }
if let image = image {
Task {
await self.imageCache.setImage(image, for: localIdentifier)
await MainActor.run {
completion(image)
}
}
} else {
Task {
await MainActor.run {
completion(nil)
}
}
}
} }
} }
} }
// 获取视频url // 获取视频url
func getVideoURL(localIdentifier: String, completion: @escaping (URL?) -> Void) { func getVideoURL(localIdentifier: String, completion: @escaping (URL?) -> Void) {
// 1. 通过本地标识符获取 PHAsset // 1. 通过本地标识符获取 PHAsset
...@@ -361,24 +572,24 @@ extension PhotoManager{ ...@@ -361,24 +572,24 @@ extension PhotoManager{
} }
// 获取所有资产的总数 // 获取所有资产的总数
func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) { // func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) {
DispatchQueue.global(qos: .background).async { // DispatchQueue.global(qos: .background).async {
let fetchOptions = PHFetchOptions() // let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] // fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
//
let allAssets = PHAsset.fetchAssets(with: .image, options: fetchOptions) // let allAssets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var assets = [PHAsset]() // var assets = [PHAsset]()
//
allAssets.enumerateObjects { (asset, _, _) in // allAssets.enumerateObjects { (asset, _, _) in
assets.append(asset) // assets.append(asset)
} // }
//
DispatchQueue.main.async { // DispatchQueue.main.async {
self.allAssets = assets // self.allAssets = assets
completion(assets) // completion(assets)
} // }
} // }
} // }
} }
......
...@@ -108,7 +108,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable { ...@@ -108,7 +108,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable {
print("开始处理分组,分组资源为:",unprocessedGroups.count) print("开始处理分组,分组资源为:",unprocessedGroups.count)
let maxConcurrency = 3 // 最大并发数 let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency) let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.count == 0{ if unprocessedGroups.count == 0{
......
...@@ -134,7 +134,7 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -134,7 +134,7 @@ class VideoSimilarJSONManager: @unchecked Sendable {
} }
// 6. 并发处理未处理的组 // 6. 并发处理未处理的组
let maxConcurrency = 4 // 视频处理较重,降低并发数 let maxConcurrency = 3 // 视频处理较重,降低并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency) let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.isEmpty { if unprocessedGroups.isEmpty {
...@@ -350,15 +350,25 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -350,15 +350,25 @@ class VideoSimilarJSONManager: @unchecked Sendable {
let vectors = assetHashes.map { hashToVector($0.hash) } let vectors = assetHashes.map { hashToVector($0.hash) }
// 3. 执行K-Means聚类 // 3. 执行K-Means聚类
let k = min(vectors.count / 2, max(2, Int(sqrt(Double(vectors.count))))) let k = min(vectors.count, 10) // 限制最多10个簇,与照片管理器保持一致
let clusters = kMeansClustering(vectors: vectors, k: k) let labels = kMeansClustering(data: vectors, k: k)
// 4. 将聚类结果转换为相似组 // 4. 将聚类结果转换为相似组
var similarGroups: [[PHAsset]] = [] var similarGroups: [[PHAsset]] = []
for cluster in clusters { var groupedAssets: [Int: [PHAsset]] = [:]
let groupAssets = cluster.indices.map { assetHashes[$0].asset }
if groupAssets.count > 1 { // 根据标签将资源分组
similarGroups.append(groupAssets) for (index, label) in labels.enumerated() {
if groupedAssets[label] == nil {
groupedAssets[label] = []
}
groupedAssets[label]?.append(assetHashes[index].asset)
}
// 只保留包含多个资源的组
for (_, group) in groupedAssets {
if group.count > 1 {
similarGroups.append(group)
} }
} }
...@@ -384,56 +394,58 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -384,56 +394,58 @@ class VideoSimilarJSONManager: @unchecked Sendable {
return vector return vector
} }
// K-Means聚类算法实现 // K-Means 聚类算法
private func kMeansClustering(vectors: [[Double]], k: Int) -> [[Int]] { func kMeansClustering(data: [[Double]], k: Int, maxIterations: Int = 100) -> [Int] {
guard vectors.count >= k else { return [Array(0..<vectors.count)] } guard data.count > 0 && k > 0 && k <= data.count else {
return []
// 1. 随机选择初始中心点 }
var centroids = (0..<k).map { _ in vectors[Int.random(in: 0..<vectors.count)] }
var clusters: [[Int]] = Array(repeating: [], count: k) var centroids = (0..<k).map { _ in data.randomElement()! }
var previousClusters: [[Int]] = [] var labels = Array(repeating: 0, count: data.count)
// 2. 迭代直到收敛或达到最大迭代次数 for _ in 0..<maxIterations {
let maxIterations = 100 var newCentroids = Array(repeating: Array(repeating: 0.0, count: data[0].count), count: k)
var iteration = 0 var clusterCounts = Array(repeating: 0, count: k)
while iteration < maxIterations {
// 清空当前聚类
clusters = Array(repeating: [], count: k)
// 3. 分配点到最近的中心点 // 分配数据点到最近的质心
for (index, vector) in vectors.enumerated() { for (i, point) in data.enumerated() {
var minDistance = Double.infinity var minDistance = Double.infinity
var closestCentroid = 0 var closestCentroidIndex = 0
for (j, centroid) in centroids.enumerated() {
for (centroidIndex, centroid) in centroids.enumerated() { let distance = euclideanDistance(point, centroid)
let distance = euclideanDistance(vector, centroid) if distance < minDistance && distance < 0.3 {
if distance < minDistance {
minDistance = distance minDistance = distance
closestCentroid = centroidIndex closestCentroidIndex = j
} }
} }
labels[i] = closestCentroidIndex
clusters[closestCentroid].append(index) newCentroids[closestCentroidIndex] = newCentroids[closestCentroidIndex].enumerated().map { index, value in
} value + point[index]
}
// 4. 检查是否收敛 clusterCounts[closestCentroidIndex] += 1
if clusters == previousClusters {
break
} }
// 5. 更新中心点 // 更新质心
var hasChanged = false
for i in 0..<k { for i in 0..<k {
guard !clusters[i].isEmpty else { continue } if clusterCounts[i] > 0 {
let clusterVectors = clusters[i].map { vectors[$0] } let newCentroid = newCentroids[i].enumerated().map { index, value in
centroids[i] = calculateMean(clusterVectors) value / Double(clusterCounts[i])
}
if newCentroid != centroids[i] {
hasChanged = true
centroids[i] = newCentroid
}
}
} }
previousClusters = clusters // 如果质心没有变化,提前结束迭代
iteration += 1 if !hasChanged {
break
}
} }
return clusters return labels
} }
// 计算欧氏距离 // 计算欧氏距离
......
...@@ -117,6 +117,7 @@ class HomeInfoViewController:BaseViewController { ...@@ -117,6 +117,7 @@ class HomeInfoViewController:BaseViewController {
self?.tablewView.deleteModel(array: imgs) self?.tablewView.deleteModel(array: imgs)
} }
}) })
self.setDefaultPage() self.setDefaultPage()
} }
......
...@@ -10,7 +10,7 @@ import Photos ...@@ -10,7 +10,7 @@ import Photos
class HomeVideoDetailController :BaseViewController { class HomeVideoDetailController :BaseViewController {
private var headerHeight : CGFloat = 98 private var headerHeight : CGFloat = 98 + 90
private var currentHeaderView: HomeVideoDetailCustomHeaderView? private var currentHeaderView: HomeVideoDetailCustomHeaderView?
......
...@@ -117,37 +117,37 @@ class HomeViewController:BaseViewController { ...@@ -117,37 +117,37 @@ class HomeViewController:BaseViewController {
// 获取主页数据 // 获取主页数据
setupData() setupData()
//
let dataUpdated = Notification.Name("DataUpdatedNotification") let dataUpdated = Notification.Name("DataUpdatedNotification")
NotificationCenter.default.addObserver(self, selector: #selector(handleDataUpdated(_:)), name: dataUpdated, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleDataUpdated(_:)), name: dataUpdated, object: nil)
//
// 视频 // // 视频
let homeVideoResourceUpdate = Notification.Name("HomeVideoResourceUpdate") // let homeVideoResourceUpdate = Notification.Name("HomeVideoResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeVideoResourceUpdate(_:)), name: homeVideoResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeVideoResourceUpdate(_:)), name: homeVideoResourceUpdate, object: nil)
//
// 相似视频 // // 相似视频
let homeSimilarVideoResourceUpdate = Notification.Name("HomeSimilarVideoResourceUpdate") // let homeSimilarVideoResourceUpdate = Notification.Name("HomeSimilarVideoResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarVideoResourceUpdate(_:)), name: homeSimilarVideoResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarVideoResourceUpdate(_:)), name: homeSimilarVideoResourceUpdate, object: nil)
//
// 相似截图 // // 相似截图
let homeSimilarScreenshotResourceUpdate = Notification.Name("HomeSimilarScreenshotResourceUpdate") // let homeSimilarScreenshotResourceUpdate = Notification.Name("HomeSimilarScreenshotResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarScreenshotResourceUpdate(_:)), name: homeSimilarScreenshotResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarScreenshotResourceUpdate(_:)), name: homeSimilarScreenshotResourceUpdate, object: nil)
//
// 屏幕截图 // // 屏幕截图
let homeScreenShotResourceUpdate = Notification.Name("HomeScreenShotResourceUpdate") // let homeScreenShotResourceUpdate = Notification.Name("HomeScreenShotResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeScreenShotResourceUpdate(_:)), name: homeScreenShotResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeScreenShotResourceUpdate(_:)), name: homeScreenShotResourceUpdate, object: nil)
//
// 其他 // // 其他
let homeOtherResourceUpdate = Notification.Name("HomeOtherResourceUpdate") // let homeOtherResourceUpdate = Notification.Name("HomeOtherResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeOtherResourceUpdate(_:)), name: homeOtherResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeOtherResourceUpdate(_:)), name: homeOtherResourceUpdate, object: nil)
//
// 重复图片 // // 重复图片
let homeDupImageResourceUpdate = Notification.Name("HomeDupImageResourceUpdate") // let homeDupImageResourceUpdate = Notification.Name("HomeDupImageResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeDupImageResourceUpdate(_:)), name: homeDupImageResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeDupImageResourceUpdate(_:)), name: homeDupImageResourceUpdate, object: nil)
//
// 相似图片 // // 相似图片
let homeSimilarImageResourceUpdate = Notification.Name("HomeSimilarImageResourceUpdate") // let homeSimilarImageResourceUpdate = Notification.Name("HomeSimilarImageResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarImageResourceUpdate(_:)), name: homeSimilarImageResourceUpdate, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarImageResourceUpdate(_:)), name: homeSimilarImageResourceUpdate, object: nil)
homeView = HomeView(frame: view.bounds) homeView = HomeView(frame: view.bounds)
homeView?.y = cWindow?.safeAreaInsets.top ?? 20 homeView?.y = cWindow?.safeAreaInsets.top ?? 20
...@@ -155,7 +155,7 @@ class HomeViewController:BaseViewController { ...@@ -155,7 +155,7 @@ class HomeViewController:BaseViewController {
homeView?.titleCallBack = {[weak self] model,type in homeView?.titleCallBack = {[weak self] model,type in
guard let self else {return} guard let self else {return}
guard let model = model else{ return }
DispatchQueue.main.async { DispatchQueue.main.async {
let vc:HomeInfoViewController = HomeInfoViewController(ids: model.assets , type: type,titleText: model.folderName) let vc:HomeInfoViewController = HomeInfoViewController(ids: model.assets , type: type,titleText: model.folderName)
self.navigationController?.pushViewController(vc, animated: true) self.navigationController?.pushViewController(vc, animated: true)
...@@ -228,27 +228,32 @@ class HomeViewController:BaseViewController { ...@@ -228,27 +228,32 @@ class HomeViewController:BaseViewController {
func setupData() { func setupData() {
// 详情数据 // 详情数据
PhotoDataManager.manager.loadFromFileSystem(resultModel: {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem(resultModel: {[weak self] model in
DispatchQueue.main.async { // DispatchQueue.main.async {
self?.homeView?.model = model // self?.homeView?.model = model
self?.homeView?.collectionView.reloadData() // self?.homeView?.collectionView.reloadData()
} // }
// 总数据 文件数量和文件大小 // // 总数据 文件数量和文件大小
PhotoAndVideoMananger.mananger.fetchAllFile {[weak self] index, FileSize in // PhotoAndVideoMananger.mananger.fetchAllFile {[weak self] index, FileSize in
guard let self else {return} // guard let self else {return}
// 相当于进度条 // // 相当于进度条
self.homeView?.model?.allFileNumber = index // self.homeView?.model?.allFileNumber = index
self.homeView?.model?.allFileSize = FileSize // self.homeView?.model?.allFileSize = FileSize
self.homeView?.setTitle() // self.homeView?.setTitle()
} completion: {[weak self] fileSize,index in // } completion: {[weak self] fileSize,index in
//
guard let self else {return} // guard let self else {return}
//
self.homeView?.model?.allFileNumber = index // self.homeView?.model?.allFileNumber = index
self.homeView?.model?.allFileSize = fileSize // self.homeView?.model?.allFileSize = fileSize
self.homeView?.setTitle() // self.homeView?.setTitle()
} // }
}) // })
// PhotoManager.shared.getBaseAssetGroup()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
...@@ -270,29 +275,29 @@ class HomeViewController:BaseViewController { ...@@ -270,29 +275,29 @@ class HomeViewController:BaseViewController {
} }
// 重新刷新下集合 // 重新刷新下集合
func reloadCollectionView(){ // func reloadCollectionView(){
DispatchQueue.main.async { // DispatchQueue.main.async {
if let collectionView = self.homeView?.collectionView { // if let collectionView = self.homeView?.collectionView {
//
for section in 0..<collectionView.numberOfSections { // for section in 0..<collectionView.numberOfSections {
for item in 0..<collectionView.numberOfItems(inSection: section) { // for item in 0..<collectionView.numberOfItems(inSection: section) {
if section == 0 || section == 1 { // if section == 0 || section == 1 {
if let cell = collectionView.cellForItem(at: IndexPath(row: item, section: section)) as? HomeTitleCollectionCell { // if let cell = collectionView.cellForItem(at: IndexPath(row: item, section: section)) as? HomeTitleCollectionCell {
UIView.transition(with: cell.collectionView!, duration: 0.3, options: .transitionCrossDissolve, animations: { // UIView.transition(with: cell.collectionView!, duration: 0.3, options: .transitionCrossDissolve, animations: {
cell.collectionView?.reloadData() // cell.collectionView?.reloadData()
}, completion: nil) // }, completion: nil)
} // }
}else{ // }else{
UIView.transition(with:collectionView, duration: 0.3, options: .transitionCrossDissolve, animations: { // UIView.transition(with:collectionView, duration: 0.3, options: .transitionCrossDissolve, animations: {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: item, section: section)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: item, section: section)])
}, completion: nil) // }, completion: nil)
} // }
} // }
} // }
} // }
} // }
//
} // }
...@@ -320,125 +325,125 @@ class HomeViewController:BaseViewController { ...@@ -320,125 +325,125 @@ class HomeViewController:BaseViewController {
} }
// 重复图片 // 重复图片
@objc func handleHomeDupImageResourceUpdate(_ notification: Notification) { // @objc func handleHomeDupImageResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.titleModelArray[0].assets.count // let count = self.homeView?.model?.titleModelArray[0].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if self.homeView?.dupHeadCell != nil && count == 0 && model.titleModelArray[0].assets.count != 0 { // if self.homeView?.dupHeadCell != nil && count == 0 && model.titleModelArray[0].assets.count != 0 {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 0)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 0)])
} // }
//
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 0, section: 0)) // let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 0, section: 0))
if let homeTitleCell = tempCell as? HomeTitleCollectionCell { // if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
var tempCount : Int = 0 // var tempCount : Int = 0
for item in model.titleModelArray[0].assets { // for item in model.titleModelArray[0].assets {
tempCount = tempCount + item.count // tempCount = tempCount + item.count
} // }
homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[0].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[0].allFileSize)))" : "(Calculating...)") // homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[0].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[0].allFileSize)))" : "(Calculating...)")
} // }
} // }
} // }
//
} // }
@objc func handleHomeSimilarImageResourceUpdate(_ notification: Notification) { // @objc func handleHomeSimilarImageResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.titleModelArray[1].assets.count // let count = self.homeView?.model?.titleModelArray[1].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if self.homeView?.similarHeadCell != nil && count == 0 && model.titleModelArray[1].assets.count != 0 { // if self.homeView?.similarHeadCell != nil && count == 0 && model.titleModelArray[1].assets.count != 0 {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 0)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 0)])
} // }
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 0)) // let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 0))
if let homeTitleCell = tempCell as? HomeTitleCollectionCell { // if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
var tempCount : Int = 0 // var tempCount : Int = 0
for item in model.titleModelArray[1].assets { // for item in model.titleModelArray[1].assets {
tempCount = tempCount + item.count // tempCount = tempCount + item.count
} // }
homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[1].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[1].allFileSize)))" : "(Calculating...)") // homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[1].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[1].allFileSize)))" : "(Calculating...)")
} // }
} // }
} // }
//
} // }
//
@objc func handleHomeVideoResourceUpdate(_ notification: Notification) { // @objc func handleHomeVideoResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.otherModelArray[0].assets.count // let count = self.homeView?.model?.otherModelArray[0].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if count == 0{ // if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 1)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 1)])
} // }
} // }
} // }
} // }
@objc func handleHomeSimilarScreenshotResourceUpdate(_ notification: Notification) { // @objc func handleHomeSimilarScreenshotResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.otherModelArray[1].assets.count // let count = self.homeView?.model?.otherModelArray[1].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if count == 0 && model.otherModelArray[1].assets.count != 0{ // if count == 0 && model.otherModelArray[1].assets.count != 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 1)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 1)])
} // }
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 1)) // let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 1))
if let otherCell = tempCell as? HomeOtherCollectionCell{ // if let otherCell = tempCell as? HomeOtherCollectionCell{
var tempCount : Int = 0 // var tempCount : Int = 0
for item in model.otherModelArray[1].assets { // for item in model.otherModelArray[1].assets {
tempCount = tempCount + item.count // tempCount = tempCount + item.count
} // }
otherCell.countLabel.text = "\(tempCount) Photos" // otherCell.countLabel.text = "\(tempCount) Photos"
} // }
//
} // }
} // }
//
} // }
//
@objc func handleHomeScreenShotResourceUpdate(_ notification: Notification) { // @objc func handleHomeScreenShotResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.otherModelArray[2].assets.count // let count = self.homeView?.model?.otherModelArray[2].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if count == 0{ // if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 2, section: 1)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 2, section: 1)])
} // }
//
} // }
} // }
//
} // }
@objc func handleHomeSimilarVideoResourceUpdate(_ notification: Notification) { // @objc func handleHomeSimilarVideoResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.otherModelArray[3].assets.count // let count = self.homeView?.model?.otherModelArray[3].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if count == 0{ // if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 3, section: 1)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 3, section: 1)])
} // }
} // }
} // }
//
} // }
@objc func handleHomeOtherResourceUpdate(_ notification: Notification) { // @objc func handleHomeOtherResourceUpdate(_ notification: Notification) {
//
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in // PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return} // guard let self else {return}
let count = self.homeView?.model?.otherModelArray[4].assets.count // let count = self.homeView?.model?.otherModelArray[4].assets.count
self.homeView?.model = model // self.homeView?.model = model
DispatchQueue.main.async { // DispatchQueue.main.async {
if count == 0{ // if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 4, section: 1)]) // self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 4, section: 1)])
} // }
} // }
} // }
} // }
@objc func handleDataUpdated(_ notification: Notification) { @objc func handleDataUpdated(_ notification: Notification) {
......
//
// HomeUIModel.swift
// PhoneManager
//
// Created by edy on 2025/5/12.
//
import Foundation
struct HomeUIModel{
var allFileNumber:Int = 0
var allFileSize:Double = 0
var duplicatesPhotos:HomePhotosModel
var similarPhotos:HomePhotosModel
var videos:HomePhotosModel
var similarScreenShots:HomePhotosModel
var screenShots:HomePhotosModel
var similarVideos:HomePhotosModel
var otherPhotos:HomePhotosModel
}
class HomePhotosModel:Codable {
var folderName:String
var allFileSize:Double
var assets:[[AssetModel]]
init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) {
self.folderName = folderName
self.allFileSize = allFileSize
self.assets = assets
}
}
enum HomeUIEnum{
case Dublicates,Similar,Videos,SimilarScreenshots,Screensshots,SimilarVideos,Other
var title:String{
switch self {
case .Dublicates:
return "Dublicates"
case .Similar:
return "Similar"
case .Videos:
return "Videos"
case .SimilarScreenshots:
return "Similar Screenshots"
case .Screensshots:
return "Screensshots"
case .SimilarVideos:
return "Similar Videos"
case .Other:
return "Other"
}
}
var index:Int{
switch self {
case .Dublicates:
return 0
case .Similar:
return 1
case .Videos:
return 0
case .SimilarScreenshots:
return 1
case .Screensshots:
return 2
case .SimilarVideos:
return 3
case .Other:
return 4
}
}
}
...@@ -27,27 +27,28 @@ class HomeCollectionViewHeader : UICollectionReusableView { ...@@ -27,27 +27,28 @@ class HomeCollectionViewHeader : UICollectionReusableView {
let bar = CustomProgressBar() let bar = CustomProgressBar()
return bar return bar
}() }()
lazy var permissionView : PMPermissionView = { lazy var permissionView : PMPermissionView = {
let view = Bundle.main.loadNibNamed("PMPermissionView", owner: nil, options: nil)?.last as! PMPermissionView let view = Bundle.main.loadNibNamed("PMPermissionView", owner: nil, options: nil)?.last as! PMPermissionView
return view return view
}() }()
// private lazy var tipLabel:UILabel = { private lazy var tipLabel:UILabel = {
// let label = UILabel() let label = UILabel()
// label.numberOfLines = 0 // 支持多行 label.numberOfLines = 0 // 支持多行
// return label return label
// }() }()
private func setupUI() { private func setupUI() {
// 文本 // 文本
// self.addSubview(self.tipLabel) self.addSubview(self.tipLabel)
// self.tipLabel.snp.makeConstraints { make in self.tipLabel.snp.makeConstraints { make in
// make.left.equalToSuperview().offset(8) make.left.equalToSuperview().offset(8)
// make.top.equalToSuperview().offset(44) make.top.equalToSuperview().offset(44)
// make.height.equalTo(17) make.height.equalTo(17)
// } }
self.addSubview(self.progressBar) self.addSubview(self.progressBar)
self.progressBar.snp.makeConstraints { make in self.progressBar.snp.makeConstraints { make in
make.top.equalTo(self.snp.top).offset(12 + 44 + 17) make.top.equalTo(self.snp.top).offset(12 + 44 + 17)
...@@ -76,7 +77,7 @@ extension HomeCollectionViewHeader{ ...@@ -76,7 +77,7 @@ extension HomeCollectionViewHeader{
/// 设置头部权限UI是否显示 /// 设置头部权限UI是否显示
func setNoPermissionHeaderPage(){ func setNoPermissionHeaderPage(){
DispatchQueue.main.async { DispatchQueue.main.async {
if Singleton.shared.photoPermission == .authorized { if PhotoManager.shared.permissionStatus == .authorized {
self.permissionView.isHidden = true self.permissionView.isHidden = true
}else { }else {
self.setFileAndCount(count: 0, fileSize: 0) self.setFileAndCount(count: 0, fileSize: 0)
...@@ -151,9 +152,9 @@ class CustomProgressBar: UIView { ...@@ -151,9 +152,9 @@ class CustomProgressBar: UIView {
} }
} }
var totalProgress: CGFloat = 0 var totalProgress: CGFloat = 0
var chaoticProgress: CGFloat = 0 { var chaoticProgress: CGFloat = 0 {
didSet{ didSet{
// Print("获取到的资源大小",chaoticProgress)
self.updateProgress() self.updateProgress()
} }
} }
...@@ -164,10 +165,11 @@ class CustomProgressBar: UIView { ...@@ -164,10 +165,11 @@ class CustomProgressBar: UIView {
let disk = WidgetPublicModel.getDiskSpace() let disk = WidgetPublicModel.getDiskSpace()
self.totalProgress = Double(disk.0) self.totalProgress = Double(disk.0)
self.usedProgress = Double(disk.0) - Double(disk.1) self.usedProgress = Double(disk.0) - Double(disk.1)
Task {
let photoData = await Double(StorageManager.manager.getPhotoResourceMemory()) // Task {
self.chaoticProgress = photoData // let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
} // self.chaoticProgress = photoData
// }
} }
...@@ -225,8 +227,9 @@ class CustomProgressBar: UIView { ...@@ -225,8 +227,9 @@ class CustomProgressBar: UIView {
private func updateProgress() { private func updateProgress() {
// 回到主线程更新 UI // 回到主线程更新 UI
DispatchQueue.main.async { DispatchQueue.main.async {
let usedProgress = CGFloat(self.usedProgress / self.totalProgress) let total = self.chaoticProgress + self.totalProgress
let chaoticProgress = CGFloat(self.chaoticProgress / self.totalProgress) let usedProgress = CGFloat((self.usedProgress) / total)
let chaoticProgress = CGFloat(self.chaoticProgress / total)
let totalProgress = usedProgress + chaoticProgress let totalProgress = usedProgress + chaoticProgress
let remainingProgress = 1 - totalProgress let remainingProgress = 1 - totalProgress
......
...@@ -121,9 +121,7 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView { ...@@ -121,9 +121,7 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
// self.tipBackView.addSubview(self.tipDetailLabel) // self.tipBackView.addSubview(self.tipDetailLabel)
// self.tipBackView.addSubview(self.saveSizeLabel) // self.tipBackView.addSubview(self.saveSizeLabel)
// self.tipBackView.addSubview(self.moreImageView) // self.tipBackView.addSubview(self.moreImageView)
self.modelTitlelabel.snp.makeConstraints { make in self.modelTitlelabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(0) make.left.equalToSuperview().offset(0)
make.top.equalToSuperview().offset(14) make.top.equalToSuperview().offset(14)
...@@ -156,6 +154,19 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView { ...@@ -156,6 +154,19 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
make.height.equalTo(20) make.height.equalTo(20)
} }
addSubview(compressionTipView)
compressionTipView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.height.equalTo(80)
make.top.equalTo(sizeLabel.snp.bottom).offset(10)
}
let tap = UITapGestureRecognizer(target: self, action: #selector(compressClick))
compressionTipView.addGestureRecognizer(tap)
// self.tipBackView.snp.makeConstraints { make in // self.tipBackView.snp.makeConstraints { make in
// make.left.equalTo(0) // make.left.equalTo(0)
// make.right.equalTo(0) // make.right.equalTo(0)
...@@ -211,4 +222,22 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView { ...@@ -211,4 +222,22 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
@objc func sortAction(){ @objc func sortAction(){
sortCallback() sortCallback()
} }
lazy var compressionTipView:VideocompressionHeadView = {
let compressionTipView = Bundle.main.loadNibNamed("VideocompressionHeadView", owner: nil)?.last as! VideocompressionHeadView
return compressionTipView
}()
@objc func compressClick(){
let vc = CompressController()
GETCURRENTNAV()?.pushViewController(vc, animated: true)
}
func GETCURRENTNAV() -> UINavigationController? {
let k = UIApplication.shared.windows.filter({$0.isKeyWindow}).first
let pre = k?.rootViewController?.presentedViewController
let rt = k?.rootViewController
return (pre as? UINavigationController) ?? ((rt as? UITabBarController)?.selectedViewController as? UINavigationController) ?? (rt as? UINavigationController)
}
} }
...@@ -23,21 +23,25 @@ class HomeView:UIView { ...@@ -23,21 +23,25 @@ class HomeView:UIView {
var similarHeadCell : HomeTitleCollectionCell? var similarHeadCell : HomeTitleCollectionCell?
var titleCallBack:(HomePhotosModel,PhotsFileType)->Void = {model,type in} var titleCallBack:(HomePhotosModel?,PhotsFileType)->Void = {model,type in}
var indexCallBack:callBack<Any> = {index in } var indexCallBack:callBack<Any> = {index in }
var otherItemCallBack : (HomePhotosModel,Int)->Void = {data,otherCellRow in} var otherItemCallBack : (HomePhotosModel,Int)->Void = {data,otherCellRow in}
var model:PhotosManagerModel? // var model:PhotosManagerModel?
var viewModel:HomeViewModel?
var isScroll = false { var isScroll = false {
didSet { didSet {
if isScroll { // if isScroll {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.tipLabel.attributedText = self.attribet // self.tipLabel.attributedText = self.attribet
} // }
} // }
} }
} }
var attribet:NSAttributedString? var attribet:NSAttributedString?
...@@ -79,26 +83,47 @@ class HomeView:UIView { ...@@ -79,26 +83,47 @@ class HomeView:UIView {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
viewModel = HomeViewModel()
setupUI() setupUI()
}
viewModel?.setupBindings()
viewModel?.homeDataChanged = {[weak self] section,row in
guard let weakSelf = self else { return }
if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeTitleCollectionCell {
// 只更新需要改变的内容
let model = weakSelf.viewModel?.headerGroup[row]
cell.reloadUIWithModel(model: model)
}
if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeOtherCollectionCell {
// 只更新需要改变的内容
let model = weakSelf.viewModel?.cardGroup[row]
cell.reloadUIWithModel(model: model)
}
weakSelf.homeHeader?.progressBar.chaoticProgress = CGFloat(weakSelf.viewModel?.totalSize ?? 0)
weakSelf.reloadHeadSize()
}
viewModel?.coverHadChange = { [weak self] in
guard let weakSelf = self else { return }
print("刷新一次封面")
weakSelf.collectionView.reloadData()
}
collectionView.reloadData()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
func setTitle() { func reloadHeadSize(){
self.attribet = self.homeHeader?.setFileAndCount(count: model?.allFileNumber ?? 0, fileSize: model?.allFileSize ?? 0) self.attribet = setFileAndCount(count: viewModel?.totalFilesCount ?? 0, fileSize: Double(viewModel?.totalSize ?? 0))
DispatchQueue.main.async {[weak self] in tipLabel.attributedText = attribet
guard let self else {return}
if isScroll == false {
self.tipLabel.attributedText = self.attribet
self.homeHeader?.setNeedsLayout()
self.homeHeader?.layoutIfNeeded()
}
}
} }
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() { private func setupUI() {
backgroundColor = .clear backgroundColor = .clear
...@@ -177,6 +202,7 @@ class HomeView:UIView { ...@@ -177,6 +202,7 @@ class HomeView:UIView {
private lazy var tipLabel:UILabel = { private lazy var tipLabel:UILabel = {
let label = UILabel() let label = UILabel()
label.numberOfLines = 0 // 支持多行 label.numberOfLines = 0 // 支持多行
label.attributedText = setFileAndCount(count:0, fileSize:0)
return label return label
}() }()
...@@ -230,10 +256,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -230,10 +256,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section { switch section {
case 0: case 0:
return model?.titleModelArray.count ?? 0 return viewModel?.headerGroup.count ?? 0 // model?.titleModelArray.count ?? 0
case 1: case 1:
return model?.otherModelArray.count ?? 0 return viewModel?.cardGroup.count ?? 0 //model?.otherModelArray.count ?? 0
default: default:
return 0 return 0
...@@ -248,20 +274,23 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -248,20 +274,23 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section { switch section {
case 0: case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeTitleCollectionCell.identifiers, for: indexPath) as! HomeTitleCollectionCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeTitleCollectionCell.identifiers, for: indexPath) as! HomeTitleCollectionCell
cell.model = model?.titleModelArray[indexPath.row] let model = viewModel?.headerGroup[indexPath.row]
cell.reloadUIWithModel(model: model)
// cell.reloadCoverData()
cell.homeTititlAction = {[weak self] idx in cell.homeTititlAction = {[weak self] idx in
guard let self = self else { return } guard let self = self else { return }
let smodel = self.model?.titleModelArray[indexPath.row]
if indexPath.row == 0 { if indexPath.row == 0 {
self.titleCallBack(smodel!,.duplicates) self.titleCallBack(model,.duplicates)
}else{ }else{
self.titleCallBack(smodel!,.similar) self.titleCallBack(model,.similar)
} }
} }
if indexPath.row == 0 { if indexPath.row == 0 {
self.dupHeadCell = cell self.dupHeadCell = cell
cell.reloadCoverData(viewModel?.dupCoverImage ?? [])
}else{ }else{
cell.reloadCoverData(viewModel?.similarCoverImage ?? [])
self.similarHeadCell = cell self.similarHeadCell = cell
} }
...@@ -274,18 +303,16 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -274,18 +303,16 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
return cell return cell
case 1: case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell
cell.imageView.image = UIImage() cell.dealMediaType(indexPath.row)
cell.playImageView.isHidden = true let model = viewModel?.cardGroup[indexPath.row]
cell.mediaType = 0 cell.reloadUIWithModel(model: model)
if indexPath.row == 0 || indexPath.row == 3{ Task {
cell.mediaType = 1 if let image = await viewModel?.coverCache.getImage(index: indexPath.row) {
cell.playImageView.isHidden = false await MainActor.run {
} cell.setCoverImageOrVideo(image: image)
if indexPath.row == 1 || indexPath.row == 2{ }
cell.mediaType = 2 }
cell.playImageView.isHidden = true
} }
cell.model = model?.otherModelArray[indexPath.row]
return cell return cell
default: default:
return UICollectionViewCell() return UICollectionViewCell()
...@@ -297,7 +324,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -297,7 +324,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
if indexPath.section == 0 { if indexPath.section == 0 {
let model = model?.titleModelArray[indexPath.row] let model = viewModel?.headerGroup[indexPath.row] //model?.titleModelArray[indexPath.row]
if model?.assets.count ?? 0 > 0 { if model?.assets.count ?? 0 > 0 {
return (model?.assets.first?.count ?? 0) > 2 ? ((collection.width - marginLR - 20) / 2.5) + 64 : ((collection.width - 2 * marginLR - 10) / 2) + 64 return (model?.assets.first?.count ?? 0) > 2 ? ((collection.width - marginLR - 20) / 2.5) + 64 : ((collection.width - 2 * marginLR - 10) / 2) + 64
...@@ -309,7 +336,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -309,7 +336,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
}else { }else {
let model = model?.otherModelArray[indexPath.row] let model = viewModel?.cardGroup[indexPath.row] //model?.otherModelArray[indexPath.row]
return itemWidth + 12 + UILabel.getSizeWith(font: UIFont.systemFont(ofSize: 16, weight: .bold),lineSpacing: 5, width: itemWidth - 32, numberOfLines: 0, content: model?.folderName ?? "").height return itemWidth + 12 + UILabel.getSizeWith(font: UIFont.systemFont(ofSize: 16, weight: .bold),lineSpacing: 5, width: itemWidth - 32, numberOfLines: 0, content: model?.folderName ?? "").height
} }
...@@ -359,16 +386,17 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -359,16 +386,17 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (indexPath.section == 0) { if (indexPath.section == 0) {
let smodel = model?.titleModelArray[indexPath.row] let smodel = viewModel?.headerGroup[indexPath.row] //model?.titleModelArray[indexPath.row]
if indexPath.row == 0 { if indexPath.row == 0 {
titleCallBack(smodel!,.duplicates) titleCallBack(smodel,.duplicates)
}else{ }else{
titleCallBack(smodel!,.similar) titleCallBack(smodel,.similar)
} }
}else{ }else{
let smodel = model?.otherModelArray[indexPath.row] guard let smodel = viewModel?.cardGroup[indexPath.row] else { return } //model?.otherModelArray[indexPath.row]
otherItemCallBack(smodel!,indexPath.row)
otherItemCallBack(smodel,indexPath.row)
} }
} }
...@@ -383,7 +411,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -383,7 +411,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
} }
header.cleanNowButtonCallback = {[weak self] in header.cleanNowButtonCallback = {[weak self] in
guard let self = self else {return} guard let self = self else {return}
let smodel = self.model?.titleModelArray[0] let smodel = self.viewModel?.headerGroup[0] //self.model?.titleModelArray[0]
let vc = HomeInfoViewController(ids: smodel!.assets , type: .duplicates,titleText: smodel!.folderName) let vc = HomeInfoViewController(ids: smodel!.assets , type: .duplicates,titleText: smodel!.folderName)
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true) self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
} }
...@@ -404,7 +432,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -404,7 +432,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
// 设置头部视图的大小 // 设置头部视图的大小
func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize { func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize {
if section == 0 { if section == 0 {
if Singleton.shared.photoPermission == .authorized { if PhotoManager.shared.permissionStatus == .authorized{
return CGSize(width: self.collectionView.width, height: 76 + (self.homeNavView?.height ?? 0)) return CGSize(width: self.collectionView.width, height: 76 + (self.homeNavView?.height ?? 0))
}else{ }else{
return CGSize(width: self.collectionView.width, height: 404) return CGSize(width: self.collectionView.width, height: 404)
...@@ -426,3 +454,44 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -426,3 +454,44 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
} }
} }
extension HomeView{
func setFileAndCount(count:Int,fileSize:Double) -> NSMutableAttributedString {
let countString = "\(count)"
let fileSizeString = formatFileSize(fileSize)
let text = countString + " files · " + fileSizeString + " of storage to clean up"
let attributedText = NSMutableAttributedString(string: text)
// 设置整体文本样式
let fullTextAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 12, weight: .regular),
.foregroundColor: UIColor(red: 0.4, green: 0.4, blue: 0.4,alpha:1)
]
attributedText.addAttributes(fullTextAttributes, range: NSRange(location: 0, length: text.count))
// 设置 "202" 为蓝色
if let range1 = text.range(of: countString) {
let nsRange1 = NSRange(range1, in: text)
attributedText.addAttributes([
.foregroundColor: UIColor.colorWithHex(hexStr: black6Color),
.font:UIFont.systemFont(ofSize: 12, weight: .semibold)
], range: nsRange1)
}
// 设置 "1.15 GB" 为蓝色
if let range2 = text.range(of: fileSizeString) {
let nsRange2 = NSRange(range2, in: text)
attributedText.addAttributes([
.font:UIFont.systemFont(ofSize: 12, weight: .semibold),
.foregroundColor: UIColor.colorWithHex(hexStr: black6Color)
], range: nsRange2)
}
return attributedText
// 将 attributedText 赋值给 UILabel
// self.tipLabel.attributedText = attributedText
}
}
...@@ -344,18 +344,23 @@ class PhotosManagerModel:Codable { ...@@ -344,18 +344,23 @@ class PhotosManagerModel:Codable {
} }
} }
class HomePhotosModel:Codable {
var folderName:String
var allFileSize:Double
var assets:[[AssetModel]]
//class HomePhotosModel:Codable {
init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) { //
self.folderName = folderName // var folderName:String
self.allFileSize = allFileSize // var allFileSize:Double
self.assets = assets // var assets:[[AssetModel]]
} //
} // init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) {
// self.folderName = folderName
// self.allFileSize = allFileSize
// self.assets = assets
// }
//}
//class AssetModel :Codable,Hashable { //class AssetModel :Codable,Hashable {
......
//
// VideocompressionHeadView.swift
// PhoneManager
//
// Created by edy on 2025/5/12.
//
import UIKit
class VideocompressionHeadView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 8
layer.masksToBounds = true
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="Jza-pY-4dR" customClass="VideocompressionHeadView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="558" height="148"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_video_compress" translatesAutoresizingMaskIntoConstraints="NO" id="vvL-sB-aRX">
<rect key="frame" x="267" y="8" width="24.333333333333314" height="24"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Video compression" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qxg-Hm-QKr">
<rect key="frame" x="214.66666666666663" y="34" width="129" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="fLR-6a-Hmq"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1GB" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rDm-i5-MN1">
<rect key="frame" x="516" y="0.0" width="42" height="21"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="K4a-jV-Wu7"/>
<constraint firstAttribute="width" constant="42" id="ijO-3x-MyS"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Click to start the process" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YY1-sb-PDU">
<rect key="frame" x="215.66666666666666" y="56" width="126.66666666666666" height="12"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="10"/>
<color key="textColor" red="0.066666666669999999" green="0.066666666669999999" blue="0.066666666669999999" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="JJA-7d-VyB"/>
<color key="backgroundColor" red="0.90196078430000004" green="0.95294117649999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="rDm-i5-MN1" secondAttribute="trailing" id="5LW-mT-Byz"/>
<constraint firstItem="Qxg-Hm-QKr" firstAttribute="top" secondItem="vvL-sB-aRX" secondAttribute="bottom" constant="2" id="B0q-UT-daI"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="centerX" secondItem="Jza-pY-4dR" secondAttribute="centerX" id="C5w-xg-vu7"/>
<constraint firstItem="Qxg-Hm-QKr" firstAttribute="centerX" secondItem="Jza-pY-4dR" secondAttribute="centerX" id="RT9-gS-gWD"/>
<constraint firstItem="YY1-sb-PDU" firstAttribute="centerX" secondItem="Qxg-Hm-QKr" secondAttribute="centerX" id="UKF-3f-8h1"/>
<constraint firstItem="rDm-i5-MN1" firstAttribute="top" secondItem="Jza-pY-4dR" secondAttribute="top" id="Xvi-LX-6zw"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="top" secondItem="Jza-pY-4dR" secondAttribute="top" constant="8" id="oFO-rz-t49"/>
<constraint firstItem="YY1-sb-PDU" firstAttribute="top" secondItem="Qxg-Hm-QKr" secondAttribute="bottom" id="rg1-pq-IPU"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="centerX" secondItem="Qxg-Hm-QKr" secondAttribute="centerX" id="uEJ-q5-lB3"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="187.78625954198472" y="250"/>
</view>
</objects>
<resources>
<image name="icon_video_compress" width="24.333333969116211" height="24"/>
</resources>
</document>
...@@ -99,7 +99,7 @@ class HomeOtherCollectionCell: UICollectionViewCell { ...@@ -99,7 +99,7 @@ class HomeOtherCollectionCell: UICollectionViewCell {
self.contentView.addSubview(self.titleLabel) self.contentView.addSubview(self.titleLabel)
self.contentView.addSubview(self.imageView) self.contentView.addSubview(self.imageView)
self.contentView.addSubview(self.playerView) self.contentView.addSubview(self.playerView)
// self.contentView.addSubview(self.playImageView) self.contentView.addSubview(self.playImageView)
self.playerView.addSubview(self.infoBackView) self.playerView.addSubview(self.infoBackView)
self.infoBackView.addSubview(self.countLabel) self.infoBackView.addSubview(self.countLabel)
...@@ -111,106 +111,208 @@ class HomeOtherCollectionCell: UICollectionViewCell { ...@@ -111,106 +111,208 @@ class HomeOtherCollectionCell: UICollectionViewCell {
self.playerView.resume() self.playerView.resume()
} }
var firstID:String?
var model:HomePhotosModel? { var model:HomePhotosModel? {
didSet { didSet {
guard let model else {return} // guard let model else {return}
DispatchQueue.main.async { //
// 设置标题 // // 设置标题
self.titleLabel.text = model.folderName // self.titleLabel.text = model.folderName
// 获取数量 // // 获取数量
var count = 0 // var count = 0
for item in model.assets { // for item in model.assets {
count = count + item.count // count = count + item.count
} // }
// 设置数量文字 // // 设置数量文字
if self.mediaType == 0 || self.mediaType == 2{ // if self.mediaType == 0 || self.mediaType == 2{
self.countLabel.text = "\(count) Photos" // self.countLabel.text = "\(count) Photos"
}else{ // }else{
self.countLabel.text = "\(count) Videos" // self.countLabel.text = "\(count) Videos"
} // }
// 设置文件大小文字 // // 设置文件大小文字
let sizeKB : Double = model.allFileSize/1000 // let sizeKB : Double = model.allFileSize/1000
if sizeKB < 1000{ // if sizeKB < 1000{
self.sizeLabel.text = String(format: "(%.2lf) KB" ,sizeKB) // self.sizeLabel.text = String(format: "(%.2lf) KB" ,sizeKB)
}else if sizeKB < (1000 * 1000) && sizeKB > 1000{ // }else if sizeKB < (1000 * 1000) && sizeKB > 1000{
self.sizeLabel.text = String(format: "(%.2lf) MB" ,sizeKB/1000) // self.sizeLabel.text = String(format: "(%.2lf) MB" ,sizeKB/1000)
}else{ // }else{
self.sizeLabel.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000)) // self.sizeLabel.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000))
} // }
}
//let currentImageView = imageView
//
// guard let firstLocalIdentifier = model.firstLocalIdentifier else{
// return
// }
//
// let tempImage = imageView
// PhotoManager.shared.getImage(localIdentifier:firstLocalIdentifier, completion: { image in
// // guard let weakSelf = self else { return }
// tempImage.image = image
// })
guard let asset = model.assets.first?.first else { // if self.mediaType == 0 || self.mediaType == 2 {}
DispatchQueue.main.async {
if self.mediaType == 0 {
self.imageView.image = UIImage(named: "othermoren")
}else if self.mediaType == 2 {
self.imageView.image = UIImage(named: "photosmoren") // DispatchQueue.main.async {
} else{ // // 设置标题
self.imageView.image = UIImage(named: "videosmoren") // self.titleLabel.text = model.folderName
} // // 获取数量
} // var count = 0
return // for item in model.assets {
} // count = count + item.count
if self.mediaType == 0 || self.mediaType == 2 { // }
let image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: asset.localIdentifier) // // 设置数量文字
DispatchQueue.main.async {[weak self] in // if self.mediaType == 0 || self.mediaType == 2{
guard let self else {return} // self.countLabel.text = "\(count) Photos"
imageView.image = image // }else{
} // self.countLabel.text = "\(count) Videos"
}else{ // }
// // 设置文件大小文字
// 定义请求选项来获取视频的第一帧 // let sizeKB : Double = model.allFileSize/1000
let options = PHImageRequestOptions() // if sizeKB < 1000{
// 获取当前版本的照片或视频 // self.sizeLabel.text = String(format: "(%.2lf) KB" ,sizeKB)
options.version = .current // }else if sizeKB < (1000 * 1000) && sizeKB > 1000{
// 尽可能快地提供结果 // self.sizeLabel.text = String(format: "(%.2lf) MB" ,sizeKB/1000)
options.deliveryMode = .highQualityFormat // }else{
// 允许从iCloud请求 // self.sizeLabel.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000))
options.isNetworkAccessAllowed = true // }
// 异步请求 // }
options.isSynchronous = false
// 从 PHAsset 获取 AVAsset
if let videoAsset = PhotoAndVideoMananger.mananger.getPHAsssetwithID(ids: [asset.localIdentifier]){ // guard let asset = model.assets.first?.first else {
// 使用requestImageForAsset方法请求视频的第一帧图片 // DispatchQueue.main.async {
// if self.mediaType == 0 {
let options = PHVideoRequestOptions() // self.imageView.image = UIImage(named: "othermoren")
options.version = .current // }else if self.mediaType == 2 {
options.deliveryMode = .automatic // 根据需求调整视频质量:ml-citation{ref="2,7" data="citationList"} // self.imageView.image = UIImage(named: "photosmoren")
// } else{
PHImageManager.default().requestAVAsset( // self.imageView.image = UIImage(named: "videosmoren")
forVideo: videoAsset,
options: options
) { (avAsset, audioMix, info) in
guard let avAsset = avAsset as? AVURLAsset else { return }
let videoURL = avAsset.url
print("视频地址: \(videoURL)")
DispatchQueue.main.async {
self.playerView.playVideo(from: videoURL)
}
}
// PHImageManager.default().requestImage(for: videoAsset, targetSize: CGSize(width: 400, height: 400), contentMode: PHImageContentMode.aspectFit, options: options) { image, _ in
// // 处理获取到的图片
// if let thumbnailImage = image {
// // 使用获取到的图片,例如显示在UIImageView上
// DispatchQueue.main.async {
// // 确保在主线程更新UI
// self.imageView.image = thumbnailImage
// }
// } else {
// self.imageView.image = UIImage(named: "img_vedio_defpage")
// print("无法获取图片")
// }
// } // }
} // }
// return
// }
// if self.mediaType == 0 || self.mediaType == 2 {
// let image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: asset.localIdentifier)
// DispatchQueue.main.async {[weak self] in
// guard let self else {return}
// imageView.image = image
// }
// }else{
//
// // 定义请求选项来获取视频的第一帧
// let options = PHImageRequestOptions()
// // 获取当前版本的照片或视频
// options.version = .current
// // 尽可能快地提供结果
// options.deliveryMode = .highQualityFormat
// // 允许从iCloud请求
// options.isNetworkAccessAllowed = true
// // 异步请求
// options.isSynchronous = false
// // 从 PHAsset 获取 AVAsset
// if let videoAsset = PhotoAndVideoMananger.mananger.getPHAsssetwithID(ids: [asset.localIdentifier]){
// // 使用requestImageForAsset方法请求视频的第一帧图片
//
// let options = PHVideoRequestOptions()
// options.version = .current
// options.deliveryMode = .automatic // 根据需求调整视频质量:ml-citation{ref="2,7" data="citationList"}
//
// PHImageManager.default().requestAVAsset(
// forVideo: videoAsset,
// options: options
// ) { (avAsset, audioMix, info) in
// guard let avAsset = avAsset as? AVURLAsset else { return }
// let videoURL = avAsset.url
// print("视频地址: \(videoURL)")
// DispatchQueue.main.async {
// self.playerView.playVideo(from: videoURL)
// }
// }
//// PHImageManager.default().requestImage(for: videoAsset, targetSize: CGSize(width: 400, height: 400), contentMode: PHImageContentMode.aspectFit, options: options) { image, _ in
//// // 处理获取到的图片
//// if let thumbnailImage = image {
//// // 使用获取到的图片,例如显示在UIImageView上
//// DispatchQueue.main.async {
//// // 确保在主线程更新UI
//// self.imageView.image = thumbnailImage
//// }
//// } else {
//// self.imageView.image = UIImage(named: "img_vedio_defpage")
//// print("无法获取图片")
//// }
//// }
// }
// }
}
}
func reloadUIWithModel(model:HomePhotosModel?){
guard let model else {return}
// 设置标题
self.titleLabel.text = model.folderName
// 获取数量
var count = 0
for item in model.assets {
count = count + item.count
}
// 设置数量文字
if self.mediaType == 0 || self.mediaType == 2{
self.countLabel.text = "\(count) Photos"
}else{
self.countLabel.text = "\(count) Videos"
}
// 设置文件大小文字
let sizeKB : Double = model.allFileSize/1000
if sizeKB < 1000{
self.sizeLabel.text = String(format: "(%.2lf) KB" ,sizeKB)
}else if sizeKB < (1000 * 1000) && sizeKB > 1000{
self.sizeLabel.text = String(format: "(%.2lf) MB" ,sizeKB/1000)
}else{
self.sizeLabel.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000))
}
}
func setCoverImageOrVideo(image:UIImage?){
if image == nil{
if self.mediaType == 0 {
self.imageView.image = UIImage(named: "othermoren")
}else if self.mediaType == 2 {
self.imageView.image = UIImage(named: "photosmoren")
} else{
self.imageView.image = UIImage(named: "videosmoren")
} }
}else{
imageView.image = image
} }
} }
func dealMediaType(_ row:Int){
if row == 0 || row == 3{
mediaType = 1
playImageView.isHidden = false
}else if row == 1 || row == 2{
mediaType = 2
playImageView.isHidden = true
}else{
mediaType = 0
playImageView.isHidden = true
}
}
// MARK: - Configuration // MARK: - Configuration
func configure(with image: UIImage?, count: Int, size: String) { func configure(with image: UIImage?, count: Int, size: String) {
imageView.image = image imageView.image = image
...@@ -241,10 +343,10 @@ class HomeOtherCollectionCell: UICollectionViewCell { ...@@ -241,10 +343,10 @@ class HomeOtherCollectionCell: UICollectionViewCell {
make.left.right.bottom.equalToSuperview() make.left.right.bottom.equalToSuperview()
make.height.equalTo(40) make.height.equalTo(40)
} }
// self.playImageView.snp.makeConstraints { make in self.playImageView.snp.makeConstraints { make in
// make.width.height.equalTo(43) make.width.height.equalTo(43)
// make.center.equalTo(self.imageView.snp.center) make.center.equalTo(self.imageView.snp.center)
// } }
self.playerView.snp.makeConstraints { make in self.playerView.snp.makeConstraints { make in
make.left.right.top.bottom.equalTo(imageView) make.left.right.top.bottom.equalTo(imageView)
} }
......
...@@ -23,7 +23,10 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -23,7 +23,10 @@ class HomeTitleCollectionCell:UICollectionViewCell {
private var assetsModels:[ImageCollectionModel] = [] private var assetsModels:[ImageCollectionModel] = []
var homeTititlAction:((_ idx:Int)->Void) = { idx in} var homeTititlAction:((_ idx:Int)->Void) = { idx in}
var firstID:String?
var hadLoadFirst:((Bool) ->Void)?
override init(frame: CGRect) { override init(frame: CGRect) {
...@@ -87,36 +90,56 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -87,36 +90,56 @@ class HomeTitleCollectionCell:UICollectionViewCell {
didSet { didSet {
guard let model else {return} // guard let model else {return}
//
titleLabel?.text = model.folderName // titleLabel?.text = model.folderName
titleLabel?.sizeToFit() // titleLabel?.sizeToFit()
//
var count = 0 // var count = 0
//
for array in model.assets { // for array in model.assets {
//
count += array.count // count += array.count
} // }
//
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)") // fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit() // fileLabel?.sizeToFit()
//
assetsModels = [] // assetsModels = model.homeAssetModel.map({ asset in
// return ImageCollectionModel(asset: asset)
for asset in model.assets.first ?? [] { // })
//
let smodel = ImageCollectionModel(asset: asset) // collectionView?.reloadData()
assetsModels.append(smodel) }
} }
func reloadUIWithModel(model:HomePhotosModel?){
guard let model = model else { return }
self.model = model
titleLabel?.text = model.folderName
titleLabel?.sizeToFit()
var count = 0
for array in model.assets {
DispatchQueue.main.async {[weak self] in count += array.count
guard let self else {return}
self.collectionView?.reloadData()
}
} }
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit()
}
func reloadCoverData(_ assets:[AssetModel]){
assetsModels.removeAll()
assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model)
})
collectionView?.reloadData()
} }
override func layoutSubviews() { override func layoutSubviews() {
...@@ -142,14 +165,16 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -142,14 +165,16 @@ class HomeTitleCollectionCell:UICollectionViewCell {
make.left.equalToSuperview().offset(16) make.left.equalToSuperview().offset(16)
make.bottom.equalToSuperview().offset(-16) make.bottom.equalToSuperview().offset(-16)
make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32) make.width.equalTo(width-32)
make.height.equalToSuperview().offset(-64) make.height.equalTo(height-64)
// make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32)
// make.height.equalToSuperview().offset(-64)
}) })
nextImage?.snp.makeConstraints({ make in nextImage?.snp.makeConstraints({ make in
make.centerY.equalTo(fileLabel!) make.centerY.equalTo(fileLabel!)
make.right.equalToSuperview().offset(-12) make.right.equalTo(-12)
make.width.height.equalTo(20) make.width.height.equalTo(20)
}) })
} }
......
import Foundation
import Combine
class HomeViewModel {
// 封面图片资源缓存
actor CoverCacheActor {
private var cardCoverImage:[UIImage?] = [
UIImage.init(named: "videosmoren"),
UIImage.init(named: "photosmoren"),
UIImage.init(named: "photosmoren"),
UIImage.init(named: "videosmoren"),
UIImage.init(named: "othermoren")
]
func getImage(index:Int) ->UIImage?{
return cardCoverImage.safeGet(index: index) ?? nil
}
func setImage(index:Int,image:UIImage?) {
guard let image = image else{
return
}
if cardCoverImage.count > index{
cardCoverImage[index] = image
}
}
}
private var photoManager = PhotoManager.shared
let coverCache = CoverCacheActor()
// 相册资源总大小
var totalSize:Int64{
return videoSize + otherSize + screentSize
}
var videoSize:Int64 = 0
var otherSize:Int64 = 0
var screentSize:Int64 = 0
var totalFilesCount:Int = 0
// 首页UI数据结构
var headerGroup:[HomePhotosModel] = [
HomePhotosModel.init(folderName: HomeUIEnum.Dublicates.title, allFileSize: 0, assets: []),
HomePhotosModel.init(folderName: HomeUIEnum.Similar.title, allFileSize: 0, assets: [])
]
var cardGroup:[HomePhotosModel] = [
HomePhotosModel.init(folderName: HomeUIEnum.Videos.title, allFileSize: 0, assets: []),
HomePhotosModel.init(folderName: HomeUIEnum.SimilarScreenshots.title, allFileSize: 0, assets: []),
HomePhotosModel.init(folderName: HomeUIEnum.Screensshots.title, allFileSize: 0, assets: []),
HomePhotosModel.init(folderName: HomeUIEnum.SimilarVideos.title, allFileSize: 0, assets: []),
HomePhotosModel.init(folderName: HomeUIEnum.Other.title, allFileSize: 0, assets: []),
]
var hadLoad = false
// 数据获取回调
var homeDataChanged:((_ section:Int,_ row:Int) ->Void)?
// 封面获取回调
var coverHadChange:(() ->Void)?
// 添加状态变化的回调闭包
var onLoadingStateChanged: ((BaseDataLoadingState) -> Void)?
var dupCoverImage:[AssetModel] = []
var similarCoverImage:[AssetModel] = []
func setupBindings() {
NotificationCenter.default.addObserver(forName: .getBaseAssetsSuccess, object: nil, queue: nil) {[weak self] _ in
guard let weakSelf = self else { return }
weakSelf.configBaseData()
}
//处理 homeview还未创建就发送通知的情况,通过hadLoad判断是否加载过
configBaseData()
}
func configBaseData(){
print("监听拿到基本资源",photoManager.baseDataLoadingState,hadLoad)
guard case .loaded = photoManager.baseDataLoadingState,hadLoad == false else{
return
}
totalFilesCount = photoManager.allAssets.count
hadLoad = true
photoManager.convertScreenShotModels {[weak self] screens, size in
guard let weakSelf = self else { return }
let type = HomeUIEnum.Screensshots
weakSelf.cardGroup[type.index] = HomePhotosModel.init(folderName: type.title, allFileSize: Double(size), assets: [screens])
weakSelf.getCoverImage(type: type, identifier: screens.first?.localIdentifier)
weakSelf.screentSize = size
weakSelf.homeDataChanged?(1,type.index)
}
photoManager.convertOtherPhotoModels {[weak self] others, size in
guard let weakSelf = self else { return }
let type = HomeUIEnum.Other
weakSelf.cardGroup[type.index] = HomePhotosModel.init(folderName: type.title, allFileSize: Double(size), assets: [others])
weakSelf.getCoverImage(type: type, identifier: others.first?.localIdentifier)
weakSelf.otherSize = size
weakSelf.homeDataChanged?(1,type.index)
}
photoManager.convertVideoModels {[weak self] videos, size in
guard let weakSelf = self else { return }
let type = HomeUIEnum.Videos
weakSelf.cardGroup[type.index] = HomePhotosModel.init(folderName: type.title, allFileSize: Double(size), assets: [videos])
weakSelf.getCoverImage(type: type, identifier: videos.first?.localIdentifier)
weakSelf.videoSize = size
weakSelf.homeDataChanged?(1,type.index)
}
startMainTask()
}
func startMainTask(){
DispatchQueue.main.async {[weak self] in
guard let weakSelf = self else { return }
weakSelf.getSimilarOptimizer()
weakSelf.getSimilarScreenOptimizer()
weakSelf.getSimilarVideoOptimizer()
// weakSelf.getGroupDuplicateImages()
}
}
// 获取相似图片
@MainActor func getSimilarOptimizer(){
let type = HomeUIEnum.Similar
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
PhotoSimilarManager.shared.findSimilarAssets(in: photoManager.otherAssets) {[weak self] group in
guard let weakSelf = self else { return }
currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize}
// weakSelf.totalSize += Int64(currentSize)
weakSelf.headerGroup[type.index].assets = currentGorup
weakSelf.headerGroup[type.index].allFileSize = currentSize
if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.similarCoverImage = group
weakSelf.coverHadChange?()
}
}
weakSelf.homeDataChanged?(0,type.index)
} completionHandler: { totalGroup in
print("获取相似图片完成",totalGroup.count)
}
}
// 获取相似截图
@MainActor func getSimilarScreenOptimizer(){
let type = HomeUIEnum.SimilarScreenshots
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
ScreenshotSimilarJSONManager.shared.findSimilarAssets(in: photoManager.screenShotAssets) {[weak self] group in
guard let weakSelf = self else { return }
currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize}
// weakSelf.totalSize += Int64(currentSize)
weakSelf.cardGroup[type.index].assets = currentGorup
weakSelf.cardGroup[type.index].allFileSize = currentSize
if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.getCoverImage(type: type, identifier: firstId)
}
}
weakSelf.homeDataChanged?(1,type.index)
} completionHandler: { totalGroup in
print("获取相似截图完成",totalGroup.count)
}
}
@MainActor func getSimilarVideoOptimizer(){
let type = HomeUIEnum.SimilarVideos
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
VideoSimilarJSONManager.shared.findSimilarVideos(in: photoManager.videoAssets) {[weak self] group in
guard let weakSelf = self else { return }
currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize}
weakSelf.cardGroup[type.index].assets = currentGorup
weakSelf.cardGroup[type.index].allFileSize = currentSize
// weakSelf.totalSize += Int64(currentSize)
if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.getCoverImage(type: type, identifier: firstId)
}
}
weakSelf.homeDataChanged?(1,type.index)
} completionHandler: { totalGroup in
print("获取相似视频完成",totalGroup.count)
}
}
// 获取重复图片
@MainActor func getGroupDuplicateImages(){
let type = HomeUIEnum.Dublicates
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
PhotoDuplicateManager.shared.findDuplicateAssets(in: photoManager.photosAssets, mediaType: .photo) {[weak self] group in
guard let weakSelf = self else { return }
currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize}
// weakSelf.totalSize += Int64(currentSize)
weakSelf.headerGroup[type.index].assets = currentGorup
weakSelf.headerGroup[type.index].allFileSize = currentSize
if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.dupCoverImage = group
weakSelf.coverHadChange?()
}
}
weakSelf.homeDataChanged?(0,type.index)
} completionHandler: { totalGroup in
print("获取重复完成",totalGroup.count)
}
}
func getCoverImage(type:HomeUIEnum,identifier:String?){
guard let identifier = identifier else{
return
}
print("执行一次\(type.title)获取封面,id=\(identifier)")
Task {
PhotoManager.shared.getImage(localIdentifier:identifier,completion: { [weak self] image in
guard let self = self else { return }
Task {
switch type {
case .Dublicates:
break
case .Similar:
break
default:
await self.coverCache.setImage(index: type.index, image: image)
await MainActor.run {
self.coverHadChange?()
}
}
}
})
}
}
}
...@@ -10,5 +10,9 @@ import Foundation ...@@ -10,5 +10,9 @@ import Foundation
extension NSNotification.Name { extension NSNotification.Name {
//垃圾桶分页页面滑动监听
static let trashPageScroll: NSNotification.Name = NSNotification.Name(rawValue: "trashPageScroll") static let trashPageScroll: NSNotification.Name = NSNotification.Name(rawValue: "trashPageScroll")
//监听拿到基本相册资源
static let getBaseAssetsSuccess: NSNotification.Name = NSNotification.Name(rawValue: "getBaseAssetsSuccess")
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment