Commit 3349dd14 authored by shenyong's avatar shenyong

首页数据获取

parent 21848fa1
......@@ -46,6 +46,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// 初始化广告SDK
AdvManager.shared.initAdertisementSDK()
PhotoManager.shared.config()
return true
}
......@@ -79,22 +81,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
/// 首次进来开始请求首页数据
func findHomeData(){
PhotoAndVideoMananger.getPrivacy {[weak self] status in
guard let self else {return}
if let photoStatus = status as? PrivacyType {
Singleton.shared.photoPermission = photoStatus
if photoStatus == .authorized {
// 有授权加载数据
PhotoAndVideoMananger.mananger.setAssets()
// 读取缓存数据
readCacheModel()
}else{
// 没有授权,更新删除缓存数据
PhotoDataManager.manager.loadDataFromPhotos { model in}
Print("未获取授权")
}
}
}
// PhotoAndVideoMananger.getPrivacy {[weak self] status in
// guard let self else {return}
// if let photoStatus = status as? PrivacyType {
// Singleton.shared.photoPermission = photoStatus
// if photoStatus == .authorized {
// // 有授权加载数据
// PhotoAndVideoMananger.mananger.setAssets()
// // 读取缓存数据
// readCacheModel()
// }else{
// // 没有授权,更新删除缓存数据
// PhotoDataManager.manager.loadDataFromPhotos { model in}
// 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 {
switch result {
case .success(let receipt):
// 打印完整收据信息,方便调试
print("收据信息:\(receipt)")
//print("收据信息:\(receipt)")
let status = self.checkSubscriptionStatus(receiptInfo: receipt)
DispatchQueue.main.async {
......
......@@ -79,7 +79,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
return
}
let maxConcurrency = 4 // 最大并发数
let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, resolutionGroups.count / maxConcurrency)
for batchIndex in stride(from: 0, to: resolutionGroups.count, by: batchSize) {
......
......@@ -12,36 +12,72 @@ import CoreML
import Vision
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()
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() {
requestAuthorization()
}
func config(){}
// 添加 Published 属性
@Published private(set) var baseDataLoadingState: BaseDataLoadingState = .notLoaded
// 定义状态枚举
enum BaseDataLoadingState {
case notLoaded
case loading
case loaded
case failed(Error)
}
private(set) var baseDataLoadingState: BaseDataLoadingState = .notLoaded
var permissionStatus:PHAuthorizationStatus = .notDetermined
// MARK: - 基础配置
// 所有的媒体资源
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]] = []
......@@ -57,20 +93,22 @@ class PhotoManager:ObservableObject{
// 视频
var videoModels:[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
var otherModels:[AssetModel] = []
private(set) var screenShotTotalSize:Int64 = 0
private(set) var videoTotalSize:Int64 = 0
private(set) var otherTotalSize:Int64 = 0
private var currentPage: Int = 0
private let pageSize: Int = 50 // 每次加载的数量
// MARK: -基础函数
// 获取相册权限
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 {
weakSelf.permissionStatus = status
completion(status == .authorized)
}
}
......@@ -90,8 +128,10 @@ class PhotoManager:ObservableObject{
baseDataLoadingState = .loading
DispatchQueue.global(qos: .background).async {
DispatchQueue.global(qos: .background).async {[weak self] in
guard let weakSelf = self else { return }
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
......@@ -99,6 +139,8 @@ class PhotoManager:ObservableObject{
let videoAllAssets = PHAsset.fetchAssets(with: .video, options: fetchOptions)
let fetchOptionsS = PHFetchOptions()
fetchOptionsS.predicate = NSPredicate(format: "mediaSubtypes == %d", PHAssetMediaSubtype.photoScreenshot.rawValue)
let screenShotAllAssets = PHAsset.fetchAssets(with: .image, options: fetchOptionsS)
......@@ -107,31 +149,48 @@ class PhotoManager:ObservableObject{
let videoAssetsArray = videoAllAssets.objects(at: IndexSet(0..<videoAllAssets.count))
let screenShotArray = screenShotAllAssets.objects(at: IndexSet(0..<screenShotAllAssets.count))
let otherArray = photoAssetsArray.filter {!screenShotArray.contains($0) }
weakSelf.allAssets = photoAssetsArray + videoAssetsArray
print("基本数据执行完毕")
// print("基本数据执行完毕")
// 在主线程更新状态
DispatchQueue.main.async {
self.photosAssets = photoAssetsArray
self.videoAssets = videoAssetsArray
self.screenShotAssets = screenShotArray
self.otherAssets = otherArray
self.baseDataLoadingState = .loaded
weakSelf.photosAssets = photoAssetsArray
weakSelf.videoAssets = videoAssetsArray
weakSelf.screenShotAssets = screenShotArray
weakSelf.otherAssets = otherArray
weakSelf.baseDataLoadingState = .loaded
print("基本数据执行完毕")
NotificationCenter.default.post(name: .getBaseAssetsSuccess, object: nil)
}
}
}
// 转化视频模型和获取大小
func convertVideoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
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()
self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2)
self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2)
let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒")
print("视频转换总耗时: \(duration)秒")
let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "video", models: self.videoModels)
await MainActor.run {
self.videoTotalSize = videoTotalSize
complectionHandler?(self.videoModels,videoTotalSize)
......@@ -142,11 +201,25 @@ class PhotoManager:ObservableObject{
// 转化其他图片模型和获取大小
func convertOtherPhotoModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
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()
self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1)
self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1)
let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒")
let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "other", models: self.otherModels)
await MainActor.run {
self.otherTotalSize = otherTotalSize
complectionHandler?(self.otherModels,otherTotalSize)
......@@ -157,18 +230,99 @@ class PhotoManager:ObservableObject{
// 转化截图模型和获取大小
func convertScreenShotModels(complectionHandler:(([AssetModel],Int64) ->Void)?){
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()
self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1)
self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1)
let duration = CFAbsoluteTimeGetCurrent() - start
print("截图转换总耗时: \(duration)秒")
let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize})
// 保存到本地
saveToLocal(type: "screenshot", models: self.screenShotModels)
await MainActor.run {
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{
}
// 根据 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) {
// 根据 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)
Task {
// 先检查缓存
if let cachedImage = await imageCache.getImage(for: localIdentifier) {
await MainActor.run {
completion(cachedImage)
}
return
}
// 根据 localIdentifier 获取 PHAsset
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
guard let asset = fetchResult.firstObject else {
await MainActor.run {
completion(nil)
}
return
}
// 配置图片请求选项
let options = PHImageRequestOptions()
options.version = .current
options.deliveryMode = .highQualityFormat
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
func getVideoURL(localIdentifier: String, completion: @escaping (URL?) -> Void) {
// 1. 通过本地标识符获取 PHAsset
......@@ -361,24 +572,24 @@ extension PhotoManager{
}
// 获取所有资产的总数
func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) {
DispatchQueue.global(qos: .background).async {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let allAssets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var assets = [PHAsset]()
allAssets.enumerateObjects { (asset, _, _) in
assets.append(asset)
}
DispatchQueue.main.async {
self.allAssets = assets
completion(assets)
}
}
}
// func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) {
// DispatchQueue.global(qos: .background).async {
// let fetchOptions = PHFetchOptions()
// fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
//
// let allAssets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
// var assets = [PHAsset]()
//
// allAssets.enumerateObjects { (asset, _, _) in
// assets.append(asset)
// }
//
// DispatchQueue.main.async {
// self.allAssets = assets
// completion(assets)
// }
// }
// }
}
......
......@@ -108,7 +108,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable {
print("开始处理分组,分组资源为:",unprocessedGroups.count)
let maxConcurrency = 3 // 最大并发数
let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.count == 0{
......
......@@ -134,7 +134,7 @@ class VideoSimilarJSONManager: @unchecked Sendable {
}
// 6. 并发处理未处理的组
let maxConcurrency = 4 // 视频处理较重,降低并发数
let maxConcurrency = 3 // 视频处理较重,降低并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.isEmpty {
......@@ -350,15 +350,25 @@ class VideoSimilarJSONManager: @unchecked Sendable {
let vectors = assetHashes.map { hashToVector($0.hash) }
// 3. 执行K-Means聚类
let k = min(vectors.count / 2, max(2, Int(sqrt(Double(vectors.count)))))
let clusters = kMeansClustering(vectors: vectors, k: k)
let k = min(vectors.count, 10) // 限制最多10个簇,与照片管理器保持一致
let labels = kMeansClustering(data: vectors, k: k)
// 4. 将聚类结果转换为相似组
var similarGroups: [[PHAsset]] = []
for cluster in clusters {
let groupAssets = cluster.indices.map { assetHashes[$0].asset }
if groupAssets.count > 1 {
similarGroups.append(groupAssets)
var groupedAssets: [Int: [PHAsset]] = [:]
// 根据标签将资源分组
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 {
return vector
}
// K-Means聚类算法实现
private func kMeansClustering(vectors: [[Double]], k: Int) -> [[Int]] {
guard vectors.count >= k else { return [Array(0..<vectors.count)] }
// 1. 随机选择初始中心点
var centroids = (0..<k).map { _ in vectors[Int.random(in: 0..<vectors.count)] }
var clusters: [[Int]] = Array(repeating: [], count: k)
var previousClusters: [[Int]] = []
// 2. 迭代直到收敛或达到最大迭代次数
let maxIterations = 100
var iteration = 0
while iteration < maxIterations {
// 清空当前聚类
clusters = Array(repeating: [], count: k)
// K-Means 聚类算法
func kMeansClustering(data: [[Double]], k: Int, maxIterations: Int = 100) -> [Int] {
guard data.count > 0 && k > 0 && k <= data.count else {
return []
}
var centroids = (0..<k).map { _ in data.randomElement()! }
var labels = Array(repeating: 0, count: data.count)
for _ in 0..<maxIterations {
var newCentroids = Array(repeating: Array(repeating: 0.0, count: data[0].count), count: k)
var clusterCounts = Array(repeating: 0, count: k)
// 3. 分配点到最近的中心点
for (index, vector) in vectors.enumerated() {
// 分配数据点到最近的质心
for (i, point) in data.enumerated() {
var minDistance = Double.infinity
var closestCentroid = 0
for (centroidIndex, centroid) in centroids.enumerated() {
let distance = euclideanDistance(vector, centroid)
if distance < minDistance {
var closestCentroidIndex = 0
for (j, centroid) in centroids.enumerated() {
let distance = euclideanDistance(point, centroid)
if distance < minDistance && distance < 0.3 {
minDistance = distance
closestCentroid = centroidIndex
closestCentroidIndex = j
}
}
clusters[closestCentroid].append(index)
}
// 4. 检查是否收敛
if clusters == previousClusters {
break
labels[i] = closestCentroidIndex
newCentroids[closestCentroidIndex] = newCentroids[closestCentroidIndex].enumerated().map { index, value in
value + point[index]
}
clusterCounts[closestCentroidIndex] += 1
}
// 5. 更新中心点
// 更新质心
var hasChanged = false
for i in 0..<k {
guard !clusters[i].isEmpty else { continue }
let clusterVectors = clusters[i].map { vectors[$0] }
centroids[i] = calculateMean(clusterVectors)
if clusterCounts[i] > 0 {
let newCentroid = newCentroids[i].enumerated().map { index, value in
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 {
self?.tablewView.deleteModel(array: imgs)
}
})
self.setDefaultPage()
}
......
......@@ -10,7 +10,7 @@ import Photos
class HomeVideoDetailController :BaseViewController {
private var headerHeight : CGFloat = 98
private var headerHeight : CGFloat = 98 + 90
private var currentHeaderView: HomeVideoDetailCustomHeaderView?
......
......@@ -117,37 +117,37 @@ class HomeViewController:BaseViewController {
// 获取主页数据
setupData()
//
let dataUpdated = Notification.Name("DataUpdatedNotification")
NotificationCenter.default.addObserver(self, selector: #selector(handleDataUpdated(_:)), name: dataUpdated, object: nil)
// 视频
let homeVideoResourceUpdate = Notification.Name("HomeVideoResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeVideoResourceUpdate(_:)), name: homeVideoResourceUpdate, object: nil)
// 相似视频
let homeSimilarVideoResourceUpdate = Notification.Name("HomeSimilarVideoResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarVideoResourceUpdate(_:)), name: homeSimilarVideoResourceUpdate, object: nil)
// 相似截图
let homeSimilarScreenshotResourceUpdate = Notification.Name("HomeSimilarScreenshotResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarScreenshotResourceUpdate(_:)), name: homeSimilarScreenshotResourceUpdate, object: nil)
// 屏幕截图
let homeScreenShotResourceUpdate = Notification.Name("HomeScreenShotResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeScreenShotResourceUpdate(_:)), name: homeScreenShotResourceUpdate, object: nil)
// 其他
let homeOtherResourceUpdate = Notification.Name("HomeOtherResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeOtherResourceUpdate(_:)), name: homeOtherResourceUpdate, object: nil)
// 重复图片
let homeDupImageResourceUpdate = Notification.Name("HomeDupImageResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeDupImageResourceUpdate(_:)), name: homeDupImageResourceUpdate, object: nil)
// 相似图片
let homeSimilarImageResourceUpdate = Notification.Name("HomeSimilarImageResourceUpdate")
NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarImageResourceUpdate(_:)), name: homeSimilarImageResourceUpdate, object: nil)
//
// // 视频
// let homeVideoResourceUpdate = Notification.Name("HomeVideoResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeVideoResourceUpdate(_:)), name: homeVideoResourceUpdate, object: nil)
//
// // 相似视频
// let homeSimilarVideoResourceUpdate = Notification.Name("HomeSimilarVideoResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarVideoResourceUpdate(_:)), name: homeSimilarVideoResourceUpdate, object: nil)
//
// // 相似截图
// let homeSimilarScreenshotResourceUpdate = Notification.Name("HomeSimilarScreenshotResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarScreenshotResourceUpdate(_:)), name: homeSimilarScreenshotResourceUpdate, object: nil)
//
// // 屏幕截图
// let homeScreenShotResourceUpdate = Notification.Name("HomeScreenShotResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeScreenShotResourceUpdate(_:)), name: homeScreenShotResourceUpdate, object: nil)
//
// // 其他
// let homeOtherResourceUpdate = Notification.Name("HomeOtherResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeOtherResourceUpdate(_:)), name: homeOtherResourceUpdate, object: nil)
//
// // 重复图片
// let homeDupImageResourceUpdate = Notification.Name("HomeDupImageResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeDupImageResourceUpdate(_:)), name: homeDupImageResourceUpdate, object: nil)
//
// // 相似图片
// let homeSimilarImageResourceUpdate = Notification.Name("HomeSimilarImageResourceUpdate")
// NotificationCenter.default.addObserver(self, selector: #selector(handleHomeSimilarImageResourceUpdate(_:)), name: homeSimilarImageResourceUpdate, object: nil)
homeView = HomeView(frame: view.bounds)
homeView?.y = cWindow?.safeAreaInsets.top ?? 20
......@@ -155,7 +155,7 @@ class HomeViewController:BaseViewController {
homeView?.titleCallBack = {[weak self] model,type in
guard let self else {return}
guard let model = model else{ return }
DispatchQueue.main.async {
let vc:HomeInfoViewController = HomeInfoViewController(ids: model.assets , type: type,titleText: model.folderName)
self.navigationController?.pushViewController(vc, animated: true)
......@@ -228,27 +228,32 @@ class HomeViewController:BaseViewController {
func setupData() {
// 详情数据
PhotoDataManager.manager.loadFromFileSystem(resultModel: {[weak self] model in
DispatchQueue.main.async {
self?.homeView?.model = model
self?.homeView?.collectionView.reloadData()
}
// 总数据 文件数量和文件大小
PhotoAndVideoMananger.mananger.fetchAllFile {[weak self] index, FileSize in
guard let self else {return}
// 相当于进度条
self.homeView?.model?.allFileNumber = index
self.homeView?.model?.allFileSize = FileSize
self.homeView?.setTitle()
} completion: {[weak self] fileSize,index in
guard let self else {return}
self.homeView?.model?.allFileNumber = index
self.homeView?.model?.allFileSize = fileSize
self.homeView?.setTitle()
}
})
// PhotoDataManager.manager.loadFromFileSystem(resultModel: {[weak self] model in
// DispatchQueue.main.async {
// self?.homeView?.model = model
// self?.homeView?.collectionView.reloadData()
// }
// // 总数据 文件数量和文件大小
// PhotoAndVideoMananger.mananger.fetchAllFile {[weak self] index, FileSize in
// guard let self else {return}
// // 相当于进度条
// self.homeView?.model?.allFileNumber = index
// self.homeView?.model?.allFileSize = FileSize
// self.homeView?.setTitle()
// } completion: {[weak self] fileSize,index in
//
// guard let self else {return}
//
// self.homeView?.model?.allFileNumber = index
// self.homeView?.model?.allFileSize = fileSize
// self.homeView?.setTitle()
// }
// })
// PhotoManager.shared.getBaseAssetGroup()
}
override func viewWillAppear(_ animated: Bool) {
......@@ -270,29 +275,29 @@ class HomeViewController:BaseViewController {
}
// 重新刷新下集合
func reloadCollectionView(){
DispatchQueue.main.async {
if let collectionView = self.homeView?.collectionView {
for section in 0..<collectionView.numberOfSections {
for item in 0..<collectionView.numberOfItems(inSection: section) {
if section == 0 || section == 1 {
if let cell = collectionView.cellForItem(at: IndexPath(row: item, section: section)) as? HomeTitleCollectionCell {
UIView.transition(with: cell.collectionView!, duration: 0.3, options: .transitionCrossDissolve, animations: {
cell.collectionView?.reloadData()
}, completion: nil)
}
}else{
UIView.transition(with:collectionView, duration: 0.3, options: .transitionCrossDissolve, animations: {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: item, section: section)])
}, completion: nil)
}
}
}
}
}
}
// func reloadCollectionView(){
// DispatchQueue.main.async {
// if let collectionView = self.homeView?.collectionView {
//
// for section in 0..<collectionView.numberOfSections {
// for item in 0..<collectionView.numberOfItems(inSection: section) {
// if section == 0 || section == 1 {
// if let cell = collectionView.cellForItem(at: IndexPath(row: item, section: section)) as? HomeTitleCollectionCell {
// UIView.transition(with: cell.collectionView!, duration: 0.3, options: .transitionCrossDissolve, animations: {
// cell.collectionView?.reloadData()
// }, completion: nil)
// }
// }else{
// UIView.transition(with:collectionView, duration: 0.3, options: .transitionCrossDissolve, animations: {
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: item, section: section)])
// }, completion: nil)
// }
// }
// }
// }
// }
//
// }
......@@ -320,125 +325,125 @@ class HomeViewController:BaseViewController {
}
// 重复图片
@objc func handleHomeDupImageResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.titleModelArray[0].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if self.homeView?.dupHeadCell != nil && count == 0 && model.titleModelArray[0].assets.count != 0 {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 0)])
}
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 0, section: 0))
if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
var tempCount : Int = 0
for item in model.titleModelArray[0].assets {
tempCount = tempCount + item.count
}
homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[0].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[0].allFileSize)))" : "(Calculating...)")
}
}
}
}
@objc func handleHomeSimilarImageResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.titleModelArray[1].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if self.homeView?.similarHeadCell != nil && count == 0 && model.titleModelArray[1].assets.count != 0 {
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 0)])
}
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 0))
if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
var tempCount : Int = 0
for item in model.titleModelArray[1].assets {
tempCount = tempCount + item.count
}
homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[1].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[1].allFileSize)))" : "(Calculating...)")
}
}
}
}
@objc func handleHomeVideoResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.otherModelArray[0].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 1)])
}
}
}
}
@objc func handleHomeSimilarScreenshotResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.otherModelArray[1].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if count == 0 && model.otherModelArray[1].assets.count != 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 1)])
}
let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 1))
if let otherCell = tempCell as? HomeOtherCollectionCell{
var tempCount : Int = 0
for item in model.otherModelArray[1].assets {
tempCount = tempCount + item.count
}
otherCell.countLabel.text = "\(tempCount) Photos"
}
}
}
}
@objc func handleHomeScreenShotResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.otherModelArray[2].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 2, section: 1)])
}
}
}
}
@objc func handleHomeSimilarVideoResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.otherModelArray[3].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 3, section: 1)])
}
}
}
}
@objc func handleHomeOtherResourceUpdate(_ notification: Notification) {
PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
guard let self else {return}
let count = self.homeView?.model?.otherModelArray[4].assets.count
self.homeView?.model = model
DispatchQueue.main.async {
if count == 0{
self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 4, section: 1)])
}
}
}
}
// @objc func handleHomeDupImageResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.titleModelArray[0].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if self.homeView?.dupHeadCell != nil && count == 0 && model.titleModelArray[0].assets.count != 0 {
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 0)])
// }
//
// let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 0, section: 0))
// if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
// var tempCount : Int = 0
// for item in model.titleModelArray[0].assets {
// tempCount = tempCount + item.count
// }
// homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[0].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[0].allFileSize)))" : "(Calculating...)")
// }
// }
// }
//
// }
// @objc func handleHomeSimilarImageResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.titleModelArray[1].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if self.homeView?.similarHeadCell != nil && count == 0 && model.titleModelArray[1].assets.count != 0 {
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 0)])
// }
// let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 0))
// if let homeTitleCell = tempCell as? HomeTitleCollectionCell {
// var tempCount : Int = 0
// for item in model.titleModelArray[1].assets {
// tempCount = tempCount + item.count
// }
// homeTitleCell.fileLabel?.text = "\(tempCount)" + " Photos " + (model.titleModelArray[1].allFileSize > 0 ? "(\(formatFileSize(model.titleModelArray[1].allFileSize)))" : "(Calculating...)")
// }
// }
// }
//
// }
//
// @objc func handleHomeVideoResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.otherModelArray[0].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if count == 0{
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 0, section: 1)])
// }
// }
// }
// }
// @objc func handleHomeSimilarScreenshotResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.otherModelArray[1].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if count == 0 && model.otherModelArray[1].assets.count != 0{
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 1, section: 1)])
// }
// let tempCell = self.homeView?.collectionView.cellForItem(at: IndexPath(row: 1, section: 1))
// if let otherCell = tempCell as? HomeOtherCollectionCell{
// var tempCount : Int = 0
// for item in model.otherModelArray[1].assets {
// tempCount = tempCount + item.count
// }
// otherCell.countLabel.text = "\(tempCount) Photos"
// }
//
// }
// }
//
// }
//
// @objc func handleHomeScreenShotResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.otherModelArray[2].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if count == 0{
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 2, section: 1)])
// }
//
// }
// }
//
// }
// @objc func handleHomeSimilarVideoResourceUpdate(_ notification: Notification) {
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.otherModelArray[3].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if count == 0{
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 3, section: 1)])
// }
// }
// }
//
// }
// @objc func handleHomeOtherResourceUpdate(_ notification: Notification) {
//
// PhotoDataManager.manager.loadFromFileSystem {[weak self] model in
// guard let self else {return}
// let count = self.homeView?.model?.otherModelArray[4].assets.count
// self.homeView?.model = model
// DispatchQueue.main.async {
// if count == 0{
// self.homeView?.collectionView.reloadItems(at: [IndexPath(row: 4, section: 1)])
// }
// }
// }
// }
@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 {
let bar = CustomProgressBar()
return bar
}()
lazy var permissionView : PMPermissionView = {
let view = Bundle.main.loadNibNamed("PMPermissionView", owner: nil, options: nil)?.last as! PMPermissionView
return view
}()
// private lazy var tipLabel:UILabel = {
// let label = UILabel()
// label.numberOfLines = 0 // 支持多行
// return label
// }()
private lazy var tipLabel:UILabel = {
let label = UILabel()
label.numberOfLines = 0 // 支持多行
return label
}()
private func setupUI() {
// 文本
// self.addSubview(self.tipLabel)
// self.tipLabel.snp.makeConstraints { make in
// make.left.equalToSuperview().offset(8)
// make.top.equalToSuperview().offset(44)
// make.height.equalTo(17)
// }
self.addSubview(self.tipLabel)
self.tipLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.top.equalToSuperview().offset(44)
make.height.equalTo(17)
}
self.addSubview(self.progressBar)
self.progressBar.snp.makeConstraints { make in
make.top.equalTo(self.snp.top).offset(12 + 44 + 17)
......@@ -76,7 +77,7 @@ extension HomeCollectionViewHeader{
/// 设置头部权限UI是否显示
func setNoPermissionHeaderPage(){
DispatchQueue.main.async {
if Singleton.shared.photoPermission == .authorized {
if PhotoManager.shared.permissionStatus == .authorized {
self.permissionView.isHidden = true
}else {
self.setFileAndCount(count: 0, fileSize: 0)
......@@ -151,9 +152,9 @@ class CustomProgressBar: UIView {
}
}
var totalProgress: CGFloat = 0
var chaoticProgress: CGFloat = 0 {
didSet{
// Print("获取到的资源大小",chaoticProgress)
self.updateProgress()
}
}
......@@ -164,10 +165,11 @@ class CustomProgressBar: UIView {
let disk = WidgetPublicModel.getDiskSpace()
self.totalProgress = Double(disk.0)
self.usedProgress = Double(disk.0) - Double(disk.1)
Task {
let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
self.chaoticProgress = photoData
}
// Task {
// let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
// self.chaoticProgress = photoData
// }
}
......@@ -225,8 +227,9 @@ class CustomProgressBar: UIView {
private func updateProgress() {
// 回到主线程更新 UI
DispatchQueue.main.async {
let usedProgress = CGFloat(self.usedProgress / self.totalProgress)
let chaoticProgress = CGFloat(self.chaoticProgress / self.totalProgress)
let total = self.chaoticProgress + self.totalProgress
let usedProgress = CGFloat((self.usedProgress) / total)
let chaoticProgress = CGFloat(self.chaoticProgress / total)
let totalProgress = usedProgress + chaoticProgress
let remainingProgress = 1 - totalProgress
......
......@@ -121,9 +121,7 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
// self.tipBackView.addSubview(self.tipDetailLabel)
// self.tipBackView.addSubview(self.saveSizeLabel)
// self.tipBackView.addSubview(self.moreImageView)
self.modelTitlelabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(0)
make.top.equalToSuperview().offset(14)
......@@ -156,6 +154,19 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
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
// make.left.equalTo(0)
// make.right.equalTo(0)
......@@ -211,4 +222,22 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
@objc func sortAction(){
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 {
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 otherItemCallBack : (HomePhotosModel,Int)->Void = {data,otherCellRow in}
var model:PhotosManagerModel?
// var model:PhotosManagerModel?
var viewModel:HomeViewModel?
var isScroll = false {
didSet {
if isScroll {
DispatchQueue.main.async {
self.tipLabel.attributedText = self.attribet
}
}
// if isScroll {
// DispatchQueue.main.async {
// self.tipLabel.attributedText = self.attribet
// }
// }
}
}
var attribet:NSAttributedString?
......@@ -79,26 +83,47 @@ class HomeView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
viewModel = HomeViewModel()
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() {
self.attribet = self.homeHeader?.setFileAndCount(count: model?.allFileNumber ?? 0, fileSize: model?.allFileSize ?? 0)
DispatchQueue.main.async {[weak self] in
guard let self else {return}
if isScroll == false {
self.tipLabel.attributedText = self.attribet
self.homeHeader?.setNeedsLayout()
self.homeHeader?.layoutIfNeeded()
}
}
func reloadHeadSize(){
self.attribet = setFileAndCount(count: viewModel?.totalFilesCount ?? 0, fileSize: Double(viewModel?.totalSize ?? 0))
tipLabel.attributedText = attribet
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .clear
......@@ -177,6 +202,7 @@ class HomeView:UIView {
private lazy var tipLabel:UILabel = {
let label = UILabel()
label.numberOfLines = 0 // 支持多行
label.attributedText = setFileAndCount(count:0, fileSize:0)
return label
}()
......@@ -230,10 +256,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section {
case 0:
return model?.titleModelArray.count ?? 0
return viewModel?.headerGroup.count ?? 0 // model?.titleModelArray.count ?? 0
case 1:
return model?.otherModelArray.count ?? 0
return viewModel?.cardGroup.count ?? 0 //model?.otherModelArray.count ?? 0
default:
return 0
......@@ -248,20 +274,23 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section {
case 0:
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
guard let self = self else { return }
let smodel = self.model?.titleModelArray[indexPath.row]
if indexPath.row == 0 {
self.titleCallBack(smodel!,.duplicates)
self.titleCallBack(model,.duplicates)
}else{
self.titleCallBack(smodel!,.similar)
self.titleCallBack(model,.similar)
}
}
if indexPath.row == 0 {
self.dupHeadCell = cell
cell.reloadCoverData(viewModel?.dupCoverImage ?? [])
}else{
cell.reloadCoverData(viewModel?.similarCoverImage ?? [])
self.similarHeadCell = cell
}
......@@ -274,18 +303,16 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
return cell
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell
cell.imageView.image = UIImage()
cell.playImageView.isHidden = true
cell.mediaType = 0
if indexPath.row == 0 || indexPath.row == 3{
cell.mediaType = 1
cell.playImageView.isHidden = false
}
if indexPath.row == 1 || indexPath.row == 2{
cell.mediaType = 2
cell.playImageView.isHidden = true
cell.dealMediaType(indexPath.row)
let model = viewModel?.cardGroup[indexPath.row]
cell.reloadUIWithModel(model: model)
Task {
if let image = await viewModel?.coverCache.getImage(index: indexPath.row) {
await MainActor.run {
cell.setCoverImageOrVideo(image: image)
}
}
}
cell.model = model?.otherModelArray[indexPath.row]
return cell
default:
return UICollectionViewCell()
......@@ -297,7 +324,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
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 {
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
}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
}
......@@ -359,16 +386,17 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (indexPath.section == 0) {
let smodel = model?.titleModelArray[indexPath.row]
let smodel = viewModel?.headerGroup[indexPath.row] //model?.titleModelArray[indexPath.row]
if indexPath.row == 0 {
titleCallBack(smodel!,.duplicates)
titleCallBack(smodel,.duplicates)
}else{
titleCallBack(smodel!,.similar)
titleCallBack(smodel,.similar)
}
}else{
let smodel = model?.otherModelArray[indexPath.row]
otherItemCallBack(smodel!,indexPath.row)
guard let smodel = viewModel?.cardGroup[indexPath.row] else { return } //model?.otherModelArray[indexPath.row]
otherItemCallBack(smodel,indexPath.row)
}
}
......@@ -383,7 +411,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
}
header.cleanNowButtonCallback = {[weak self] in
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)
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
......@@ -404,7 +432,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
// 设置头部视图的大小
func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize {
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))
}else{
return CGSize(width: self.collectionView.width, height: 404)
......@@ -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 {
}
}
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
}
}
//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
// }
//}
//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 {
self.contentView.addSubview(self.titleLabel)
self.contentView.addSubview(self.imageView)
self.contentView.addSubview(self.playerView)
// self.contentView.addSubview(self.playImageView)
self.contentView.addSubview(self.playImageView)
self.playerView.addSubview(self.infoBackView)
self.infoBackView.addSubview(self.countLabel)
......@@ -111,106 +111,208 @@ class HomeOtherCollectionCell: UICollectionViewCell {
self.playerView.resume()
}
var firstID:String?
var model:HomePhotosModel? {
didSet {
guard let model else {return}
DispatchQueue.main.async {
// 设置标题
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))
}
}
// 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))
// }
//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 {
DispatchQueue.main.async {
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")
}
}
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("无法获取图片")
// }
// if self.mediaType == 0 || self.mediaType == 2 {}
// DispatchQueue.main.async {
// // 设置标题
// 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))
// }
// }
// guard let asset = model.assets.first?.first else {
// DispatchQueue.main.async {
// 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")
// }
}
// }
// 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
func configure(with image: UIImage?, count: Int, size: String) {
imageView.image = image
......@@ -241,10 +343,10 @@ class HomeOtherCollectionCell: UICollectionViewCell {
make.left.right.bottom.equalToSuperview()
make.height.equalTo(40)
}
// self.playImageView.snp.makeConstraints { make in
// make.width.height.equalTo(43)
// make.center.equalTo(self.imageView.snp.center)
// }
self.playImageView.snp.makeConstraints { make in
make.width.height.equalTo(43)
make.center.equalTo(self.imageView.snp.center)
}
self.playerView.snp.makeConstraints { make in
make.left.right.top.bottom.equalTo(imageView)
}
......
......@@ -23,7 +23,10 @@ class HomeTitleCollectionCell:UICollectionViewCell {
private var assetsModels:[ImageCollectionModel] = []
var homeTititlAction:((_ idx:Int)->Void) = { idx in}
var firstID:String?
var hadLoadFirst:((Bool) ->Void)?
override init(frame: CGRect) {
......@@ -87,36 +90,56 @@ class HomeTitleCollectionCell:UICollectionViewCell {
didSet {
guard let model else {return}
titleLabel?.text = model.folderName
titleLabel?.sizeToFit()
var count = 0
for array in model.assets {
count += array.count
}
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit()
assetsModels = []
for asset in model.assets.first ?? [] {
let smodel = ImageCollectionModel(asset: asset)
assetsModels.append(smodel)
}
// guard let model else {return}
//
// titleLabel?.text = model.folderName
// titleLabel?.sizeToFit()
//
// var count = 0
//
// for array in model.assets {
//
// count += array.count
// }
//
// fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
// fileLabel?.sizeToFit()
//
// assetsModels = model.homeAssetModel.map({ asset in
// return ImageCollectionModel(asset: asset)
// })
//
// collectionView?.reloadData()
}
}
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
guard let self else {return}
self.collectionView?.reloadData()
}
count += array.count
}
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() {
......@@ -142,14 +165,16 @@ class HomeTitleCollectionCell:UICollectionViewCell {
make.left.equalToSuperview().offset(16)
make.bottom.equalToSuperview().offset(-16)
make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32)
make.height.equalToSuperview().offset(-64)
make.width.equalTo(width-32)
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
make.centerY.equalTo(fileLabel!)
make.right.equalToSuperview().offset(-12)
make.right.equalTo(-12)
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
extension NSNotification.Name {
//垃圾桶分页页面滑动监听
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