Commit 63c05ef8 authored by shenyong's avatar shenyong

首页数据构造修改

parent a7703dda
......@@ -45,6 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
AdvManager.shared.initAdertisementSDK()
PMEmailManager.shareManager.restore()
// 相册基本资源加载
PhotoManager.shared.config()
return true
......
......@@ -260,3 +260,14 @@ class TrashDatabase {
sqlite3_close(db)
}
}
extension TrashDatabase{
func dealHomeData(type:Int){
}
}
import Foundation
import Photos
actor AssetSizeCache {
static let shared = AssetSizeCache()
private let cache: NSCache<NSString, NSNumber> = NSCache<NSString, NSNumber>()
private let fileQueue = DispatchQueue(label: "com.assetsize.fileoperations", qos: .utility)
// 临时存储,用于跟踪修改
private var tempStorage: [String: Int64] = [:]
private var hasChanges: Bool = false
private let cachePath: String = {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("AssetSizeCache.plist").path
}()
private init() {
Task {
await loadCache()
}
}
private func loadCache() {
fileQueue.sync {
if let dict = NSDictionary(contentsOfFile: cachePath) as? [String: Int64] {
for (key, value) in dict {
cache.setObject(NSNumber(value: value), forKey: key as NSString)
tempStorage[key] = value
}
}
}
}
func getSize(for asset: PHAsset) -> Int64? {
// 优先从内存缓存中读取
if let size = tempStorage[asset.localIdentifier] {
return size
}
return cache.object(forKey: asset.localIdentifier as NSString)?.int64Value
}
func setSize(_ size: Int64, for asset: PHAsset) {
cache.setObject(NSNumber(value: size), forKey: asset.localIdentifier as NSString)
tempStorage[asset.localIdentifier] = size
hasChanges = true
}
// 手动触发保存到文件
func saveToFile() {
guard hasChanges else { return }
let path = self.cachePath
let storageToSave = self.tempStorage
fileQueue.async {
(storageToSave as NSDictionary).write(toFile: path, atomically: true)
}
hasChanges = false
}
func clearCache() {
cache.removeAllObjects()
tempStorage.removeAll()
hasChanges = true
let path = self.cachePath
fileQueue.async {
try? FileManager.default.removeItem(atPath: path)
}
}
}
//
// PhotoCleanTool.swift
// PhoneManager
//
// Created by edy on 2025/5/15.
//
import Foundation
struct PhotoCleanTool{
// app内手动删除资源
func deleteLocalResouces(for ids:[String]){
let other = PhotoManager.shared.otherModels.filter{ids.contains($0.localIdentifier)}
let screen = PhotoManager.shared.screenShotModels.filter{ids.contains($0.localIdentifier)}
let video = PhotoManager.shared.videoModels.filter{ids.contains($0.localIdentifier)}
PhotoManager.shared.otherModels = other
PhotoManager.shared.screenShotModels = screen
PhotoManager.shared.videoModels = video
}
// 删除无效资源
func cleanIneffectiveResources(){
}
}
......@@ -9,9 +9,9 @@ import Foundation
import Photos
import UIKit
@MainActor
class PhotoDuplicateManager: @unchecked Sendable {
// 移除顶层的 @MainActor
class PhotoDuplicateManager: @unchecked Sendable {
static let shared = PhotoDuplicateManager()
private let stateManager = PhotoDuplicateStateManager() // 使用新的状态管理器
private init() {}
......@@ -41,7 +41,9 @@ class PhotoDuplicateManager: @unchecked Sendable {
progressHandler: (([AssetModel]) -> Void)?,
completionHandler: (([[AssetModel]]) -> Void)?) {
Task {
Task.detached(priority: .background) { [weak self] in
guard let self = self else { return }
// 1. 加载本地数据
await loadStoredData()
print("本地数据加载完成")
......@@ -50,9 +52,6 @@ class PhotoDuplicateManager: @unchecked Sendable {
let cachedGroups = await stateManager.getAllDuplicateGroups()
print("通知已缓存的结果", cachedGroups.count)
await MainActor.run {
// for group in cachedGroups {
// progressHandler?(group.assets)
// }
let groups = cachedGroups.map{$0.assets}
loacalHandler?(groups)
}
......@@ -66,7 +65,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
// 创建特征键(分辨率+创建时间+文件大小)
let resolution = "\(asset.pixelWidth)x\(asset.pixelHeight)"
// let createTime = asset.creationDate?.timeIntervalSince1970 ?? 0
let fileSize = getAssetSize(asset)
let fileSize = await getAssetSize(asset: asset)
let featureKey = "\(resolution)_\(fileSize)"
if tempGroups[featureKey] == nil {
......@@ -149,45 +148,212 @@ class PhotoDuplicateManager: @unchecked Sendable {
completionHandler?(allGroups.map { $0.assets })
}
}
}
// MARK: - 辅助方法
// func findDuplicateAssets(
// in assets: [PHAsset],
// mediaType: MediaType = .photo,
// loacalHandler: @Sendable @escaping ([[AssetModel]]) -> Void,
// progressHandler: @Sendable @escaping ([AssetModel]) -> Void,
// completionHandler: @Sendable @escaping ([[AssetModel]]) -> Void
// ) {
// // 使用后台优先级
// Task.detached(priority: .background) { [weak self] in
// guard let self = self else { return }
//
// do {
// // 批量处理,减少主线程切换
// let batchSize = 100
// var accumulatedProgress: [AssetModel] = []
//
// for i in stride(from: 0, to: assets.count, by: batchSize) {
// let end = min(i + batchSize, assets.count)
// let batchAssets = Array(assets[i..<end])
//
// // 处理一批资源
// let results = try await self.processBatch(batchAssets)
// accumulatedProgress.append(contentsOf: results)
//
// // 每处理一定数量后才更新UI
// if accumulatedProgress.count >= 50 {
// await MainActor.run {
// progressHandler(accumulatedProgress)
// }
// accumulatedProgress.removeAll()
// }
//
// // 给主线程喘息的机会
// try await Task.sleep(nanoseconds: 1_000_000) // 1ms
// }
//
// // 1. 加载本地数据
// await self.loadStoredData()
// print("本地数据加载完成")
//
// // 2. 通知已缓存的结果
// let cachedGroups = await self.stateManager.getAllDuplicateGroups()
// print("通知已缓存的结果", cachedGroups.count)
//
// await MainActor.run {
// let groups = cachedGroups.map { $0.assets }
// loacalHandler(groups)
// }
//
// // 3. 按特征预分组(分辨率、时间、文件大小)
// let featureGroups = try await self.groupAssetsByFeatures(assets)
//
// // 如果没有需要处理的组,直接返回缓存结果
// if featureGroups.isEmpty {
// let total = cachedGroups.map { $0.assets }
// await MainActor.run {
// completionHandler(total)
// }
// return
// }
//
// // 4. 并发处理每组资源
// try await self.processFeatureGroups(featureGroups, progressHandler: progressHandler)
//
// // 5. 完成处理
// if await !self.stateManager.getPendingDuplicateGroups().isEmpty {
// await self.savePendingDuplicateGroups()
// }
//
// let allGroups = await self.stateManager.getAllDuplicateGroups()
// await MainActor.run {
// print("执行完毕")
// completionHandler(allGroups.map { $0.assets })
// }
// } catch {
// print("查找重复资源失败: \(error)")
// await MainActor.run {
// completionHandler([]) // 返回空数组或错误处理
// }
// }
// }
// }
nonisolated private func areAssetsExactlyDuplicate(_ assets: [PHAsset]) -> Bool {
guard let firstAsset = assets.first else { return false }
// 按特征分组(分辨率、文件大小等)
// private func groupAssetsByFeatures(_ assets: [PHAsset]) async throws -> [[PHAsset]] {
// var tempGroups: [String: [PHAsset]] = [:]
//
// // 使用串行队列确保线程安全
// let queue = DispatchQueue(label: "com.example.assetGrouping")
//
// // 并发处理所有资源
// await withThrowingTaskGroup(of: Void.self) { group in
// for asset in assets {
// group.addTask {
// // 创建特征键(分辨率+文件大小)
// let resolution = "\(asset.pixelWidth)x\(asset.pixelHeight)"
// let fileSize = await self.getAssetSize(asset: asset)
// let featureKey = "\(resolution)_\(fileSize)"
//
// // 使用同步队列更新共享字典
// queue.sync {
// if tempGroups[featureKey] == nil {
// tempGroups[featureKey] = []
// }
// tempGroups[featureKey]?.append(asset)
// }
// }
// }
// }
//
// // 只保留有多个资源的组
// return tempGroups.values.filter { $0.count > 1 }
// }
return assets.allSatisfy { asset in
// 检查分辨率完全相同
if asset.pixelWidth != firstAsset.pixelWidth ||
asset.pixelHeight != firstAsset.pixelHeight {
return false
}
// 处理特征分组并查找重复项
// private func processFeatureGroups(
// _ featureGroups: [[PHAsset]],
// progressHandler: (([AssetModel]) -> Void)?
// ) async throws {
// let maxConcurrency = 2 // 最大并发数
// let batchSize = max(1, featureGroups.count / maxConcurrency)
//
// for batchIndex in stride(from: 0, to: featureGroups.count, by: batchSize) {
// let endIndex = min(batchIndex + batchSize, featureGroups.count)
// let batch = Array(featureGroups[batchIndex..<endIndex])
//
// await withThrowingTaskGroup(of: Void.self) { group in
// for assets in batch {
// group.addTask { [weak self] in
// guard let self = self else { return }
//
// // 只需要计算phash值并比较
// var hashGroups: [String: [PHAsset]] = [:]
//
// // 处理单个组内的资源,控制并发
// await withThrowingTaskGroup(of: Void.self) { innerGroup in
// for asset in assets {
// innerGroup.addTask {
// if let hash = await self.getOrCalculateHash(for: asset) {
// // 使用同步队列更新共享字典
// DispatchQueue.global().sync {
// if hashGroups[hash] == nil {
// hashGroups[hash] = []
// }
// hashGroups[hash]?.append(asset)
// }
// }
// }
// }
// }
//
// // 找出phash值相同的组
// let duplicateGroups = hashGroups.values.filter { $0.count > 1 }
//
// // 处理找到的重复组
// for duplicateGroup in duplicateGroups {
// let groupId = UUID().uuidString
// let assetModels = await self.createAssetModels(from: duplicateGroup)
//
// // 通知进度
// await MainActor.run {
// progressHandler?(assetModels)
// }
//
// // 保存重复组
// await self.stateManager.appendDuplicateGroup(
// DuplicateGroupModel(groupId: groupId, assets: assetModels)
// )
//
// if await self.stateManager.shouldSavePendingGroups() {
// await self.savePendingDuplicateGroups()
// }
// }
// }
// }
// }
// }
// }
// 检查文件大小完全相同
let firstSize = getAssetSize(firstAsset)
let currentSize = getAssetSize(asset)
if firstSize != currentSize {
return false
}
// MARK: - 辅助方法
// 检查创建时间是否接近(避免连拍照片)
// if let time1 = asset.creationDate,
// let time2 = firstAsset.creationDate {
// let timeDiff = abs(time1.timeIntervalSince(time2))
// if timeDiff < 1.0 { // 1秒内的连拍照片不算重复
// return false
// nonisolated private func getAssetSize(_ asset: PHAsset) -> Int64 {
// if let resource = PHAssetResource.assetResources(for: asset).first,
// let size = resource.value(forKey: "fileSize") as? Int64 {
// return size
// }
// return 0
// }
return true
}
func getAssetSize(asset: PHAsset) async -> Int64 {
// 先尝试从缓存获取
if let cachedSize = await AssetSizeCache.shared.getSize(for: asset) {
return cachedSize
}
nonisolated private func getAssetSize(_ asset: PHAsset) -> Int64 {
// 如果缓存中没有,则计算大小
if let resource = PHAssetResource.assetResources(for: asset).first,
let size = resource.value(forKey: "fileSize") as? Int64 {
// 将计算结果存入缓存
await AssetSizeCache.shared.setSize(size, for: asset)
return size
}
return 0
}
......@@ -197,7 +363,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
for asset in assets {
modelGroup.addTask {
let size = self.getAssetSize(asset)
let size = await self.getAssetSize(asset: asset)
return AssetModel(
localIdentifier: asset.localIdentifier,
assetSize: Double(size),
......@@ -304,4 +470,47 @@ extension PhotoDuplicateManager {
let average = UInt8(pixels.reduce(0, { UInt32($0) + UInt32($1) }) / UInt32(pixels.count))
return pixels.map { $0 > average ? "1" : "0" }.joined()
}
// private func processBatch(_ assets: [PHAsset]) async throws -> [AssetModel] {
// var results: [AssetModel] = []
//
// // 按特征预分组
// let featureGroups = try await self.groupAssetsByFeatures(assets)
//
// // 处理每个特征组
// for assets in featureGroups {
// // 计算hash并分组
// var hashGroups: [String: [PHAsset]] = [:]
//
// for asset in assets {
// if let hash = await self.getOrCalculateHash(for: asset) {
// if hashGroups[hash] == nil {
// hashGroups[hash] = []
// }
// hashGroups[hash]?.append(asset)
// }
// }
//
// // 找出hash值相同的组
// let duplicateGroups = hashGroups.values.filter { $0.count > 1 }
//
// // 处理找到的重复组
// for duplicateGroup in duplicateGroups {
// let assetModels = await self.createAssetModels(from: duplicateGroup)
// results.append(contentsOf: assetModels)
//
// // 保存到状态管理器
// let groupId = UUID().uuidString
// await self.stateManager.appendDuplicateGroup(
// DuplicateGroupModel(groupId: groupId, assets: assetModels)
// )
// }
// }
//
// return results
//
// }
}
......@@ -88,6 +88,15 @@ class PhotoManager{
// 重复图片分组
var duplicateModels:[[AssetModel]] = []
// 过滤垃圾桶/保留相似图片分组
var filterSimilarModels:[[AssetModel]] = []
// 过滤垃圾桶/保留相似截图分组
var filterSimilarScreenShotModels:[[AssetModel]] = []
// 过滤垃圾桶/保留相似视频分组
var filterSimilarVideoModels:[[AssetModel]] = []
// // 过滤垃圾桶/保留重复图片分组
// var filterDuplicateModels:[[AssetModel]] = []
// 截图
var screenShotModels:[AssetModel] = []
// 视频
......@@ -95,6 +104,14 @@ class PhotoManager{
// 其他
var otherModels:[AssetModel] = []
// 过滤垃圾桶/保留截图
var filterScreenShotModels:[AssetModel] = []
// 过滤垃圾桶/保留视频
var filterVideoModels:[AssetModel] = []
// 过滤垃圾桶/保留其他
var filterOtherModels:[AssetModel] = []
private(set) var screenShotTotalSize:Int64 = 0
private(set) var videoTotalSize:Int64 = 0
private(set) var otherTotalSize:Int64 = 0
......@@ -331,13 +348,30 @@ extension PhotoManager{
// 获取文件大小
func getAssetSize(for asset:PHAsset) ->Int64{
// func getAssetSize(for asset:PHAsset) ->Int64{
// if let resource = PHAssetResource.assetResources(for: asset).first,
// let size = resource.value(forKey: "fileSize") as? Int64 {
// return size
// } else {
// return 0
// }
// }
func getAssetSize(for asset: PHAsset) async -> Int64 {
// 先尝试从缓存获取
if let cachedSize = await AssetSizeCache.shared.getSize(for: asset) {
return cachedSize
}
// 如果缓存中没有,则计算大小
if let resource = PHAssetResource.assetResources(for: asset).first,
let size = resource.value(forKey: "fileSize") as? Int64 {
// 将计算结果存入缓存
await AssetSizeCache.shared.setSize(size, for: asset)
return size
} else {
return 0
}
return 0
}
......@@ -585,6 +619,10 @@ extension PhotoManager{
}
}
func getTotalSize(source:[[AssetModel]]) ->Double{
return source.flatMap{$0}.reduce(0){$0+$1.assetSize}
}
// 获取所有资产的总数
// func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) {
// DispatchQueue.global(qos: .background).async {
......@@ -606,10 +644,9 @@ extension PhotoManager{
// }
}
extension PhotoManager{
// 批量转模型
func convertAssetsToModel(for assets: [PHAsset], mediaType: Int) async -> [AssetModel] {
let batchSize = 4 // 控制并发数量
var results: [AssetModel] = []
......@@ -624,7 +661,7 @@ extension PhotoManager{
group.addTask {
return AssetModel(
localIdentifier: asset.localIdentifier,
assetSize: Double(self.getAssetSize(for: asset)),
assetSize: Double(await self.getAssetSize(for: asset)),
createDate: asset.creationDate ?? Date(),
mediaType: mediaType
)
......@@ -640,6 +677,7 @@ extension PhotoManager{
return results
}
// 获取总数据
func getModelsData(){
Task{
let start = CFAbsoluteTimeGetCurrent()
......@@ -660,3 +698,5 @@ extension PhotoManager{
}
}
}
......@@ -10,7 +10,7 @@ import Photos
import UIKit
@MainActor
class PhotoSimilarManager: @unchecked Sendable {
static let shared = PhotoSimilarManager()
......@@ -198,7 +198,6 @@ class PhotoSimilarManager: @unchecked Sendable {
} else {
assetSize = 0
}
let model = AssetModel(
localIdentifier: asset.localIdentifier,
assetSize: assetSize,
......
......@@ -9,7 +9,7 @@ import Foundation
import Photos
import UIKit
@MainActor
class ScreenshotSimilarJSONManager: @unchecked Sendable {
static let shared = ScreenshotSimilarJSONManager()
......
......@@ -44,7 +44,7 @@ private actor VideoAssetCacheManager {
}
@MainActor
class VideoSimilarJSONManager: @unchecked Sendable {
static let shared = VideoSimilarJSONManager()
private let stateManager = VideoSimilarStateManager()
......
......@@ -162,6 +162,9 @@ class CustomProgressBar: UIView {
var chaoticProgress: CGFloat = 0 {
didSet {
guard chaoticProgress != oldValue else{
return
}
scheduleProgressUpdate()
}
}
......@@ -240,7 +243,7 @@ class CustomProgressBar: UIView {
guard let self = self else { return }
// 计算总容量和各部分比例
let total = max(self.totalProgress, 0.001) // 避免除以0
let total = max(self.totalProgress + self.chaoticProgress, 0.001) // 避免除以0
let usedRatio = min(max(self.usedProgress / total, 0), 1)
let chaoticRatio = min(max(self.chaoticProgress / total, 0), 1)
......
......@@ -31,8 +31,9 @@ class HomeView:UIView {
// var model:PhotosManagerModel?
var viewModel:HomeViewModel?
// var viewModel:HomeviewModel
private var viewModel = HomeViewModel.shared
var attribet:NSAttributedString?
......@@ -73,41 +74,51 @@ class HomeView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
viewModel = HomeViewModel()
// viewModel = HomeViewModel()
setupUI()
viewModel?.setupBindings()
viewModel.setupBindings()
viewModel?.homeDataChanged = {[weak self] section,row in
viewModel.homeDataChanged = {[weak self] section,row in
guard let weakSelf = self else { return }
DispatchQueue.main.async {
if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeTitleCollectionCell {
// 只更新需要改变的内容
let model = weakSelf.viewModel?.headerGroup[row]
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]
let model = weakSelf.viewModel.cardGroup[row]
cell.reloadUIWithModel(model: model)
}
weakSelf.homeHeader?.progressBar.chaoticProgress = CGFloat(weakSelf.viewModel?.totalSize ?? 0)
weakSelf.homeHeader?.progressBar.chaoticProgress = CGFloat(weakSelf.viewModel.totalSize)
weakSelf.reloadHeadSize()
}
viewModel?.coverHadChange = { [weak self] in
}
viewModel.reloadCellHeight = {[weak self] in
guard let weakSelf = self else { return }
print("刷新一次封面")
DispatchQueue.main.async {
weakSelf.collectionView.reloadData()
}
}
// viewModel.coverHadChange = { [weak self] in
// guard let weakSelf = self else { return }
// print("刷新一次封面")
// weakSelf.collectionView.reloadData()
// }
collectionView.reloadData()
}
func reloadHeadSize(){
self.attribet = setFileAndCount(count: viewModel?.totalFilesCount ?? 0, fileSize: Double(viewModel?.totalSize ?? 0))
self.attribet = setFileAndCount(count: viewModel.totalFilesCount, fileSize: Double(viewModel.totalSize))
tipLabel.attributedText = attribet
}
......@@ -230,10 +241,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section {
case 0:
return viewModel?.headerGroup.count ?? 0 // model?.titleModelArray.count ?? 0
return viewModel.headerGroup.count// model?.titleModelArray.count ?? 0
case 1:
return viewModel?.cardGroup.count ?? 0 //model?.otherModelArray.count ?? 0
return viewModel.cardGroup.count//model?.otherModelArray.count ?? 0
default:
return 0
......@@ -248,7 +259,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
switch section {
case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeTitleCollectionCell.identifiers, for: indexPath) as! HomeTitleCollectionCell
let model = viewModel?.headerGroup[indexPath.row]
let model = viewModel.headerGroup[indexPath.row]
cell.reloadUIWithModel(model: model)
// cell.reloadCoverData()
cell.homeTititlAction = {[weak self] idx in
......@@ -259,14 +270,14 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
self.titleCallBack(model,.similar)
}
}
cell.reloadCoverData(viewModel?.headCoverImages[indexPath.row] ?? [])
if indexPath.row == 0 {
self.dupHeadCell = cell
// cell.reloadCoverData(viewModel?.dupCoverImage ?? [])
}else{
// cell.reloadCoverData(viewModel?.similarCoverImage ?? [])
self.similarHeadCell = cell
}
// cell.reloadCoverData(viewModel.headCoverImages[indexPath.row] ?? [])
// if indexPath.row == 0 {
// self.dupHeadCell = cell
// // cell.reloadCoverData(viewModel.dupCoverImage ?? [])
// }else{
// // cell.reloadCoverData(viewModel.similarCoverImage ?? [])
// self.similarHeadCell = cell
// }
if cell.model?.assets.count ?? 0 > 0 {
cell.fileLabel?.isHidden = false
......@@ -278,15 +289,15 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell
cell.dealMediaType(indexPath.row)
let model = viewModel?.cardGroup[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)
}
}
}
// Task {
// if let image = await viewModel.coverCache.getImage(index: indexPath.row) {
// await MainActor.run {
// cell.setCoverImageOrVideo(image: image)
// }
// }
// }
return cell
default:
return UICollectionViewCell()
......@@ -298,10 +309,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
if indexPath.section == 0 {
let model = viewModel?.headerGroup[indexPath.row] //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
if model.assets.count > 0 {
return (model.assets.first?.count ?? 0) > 2 ? ((collection.width - marginLR - 20) / 2.5) + 64 : ((collection.width - 2 * marginLR - 10) / 2) + 64
}else{
return 52
......@@ -310,9 +321,9 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
}else {
let model = viewModel?.cardGroup[indexPath.row] //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
}
}
......@@ -360,7 +371,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (indexPath.section == 0) {
let smodel = viewModel?.headerGroup[indexPath.row] //model?.titleModelArray[indexPath.row]
let smodel = viewModel.headerGroup[indexPath.row] //model?.titleModelArray[indexPath.row]
if indexPath.row == 0 {
titleCallBack(smodel,.duplicates)
}else{
......@@ -368,8 +379,8 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
}
}else{
guard let smodel = viewModel?.cardGroup[indexPath.row] else { return } //model?.otherModelArray[indexPath.row]
//guard let smodel = viewModel.cardGroup[indexPath.row] else { return } //model?.otherModelArray[indexPath.row]
let smodel = viewModel.cardGroup[indexPath.row]
otherItemCallBack(smodel,indexPath.row)
}
}
......@@ -385,8 +396,8 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
}
header.cleanNowButtonCallback = {[weak self] in
guard let self = self else {return}
let smodel = self.viewModel?.headerGroup[0] //self.model?.titleModelArray[0]
let vc = HomeInfoViewController(ids: smodel!.assets , type: .duplicates,titleText: smodel!.folderName)
let smodel = self.viewModel.headerGroup[0]
let vc = HomeInfoViewController(ids: smodel.assets , type: .duplicates,titleText: smodel.folderName)
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
if indexPath.section != 0 {
......
......@@ -281,6 +281,14 @@ class HomeOtherCollectionCell: UICollectionViewCell {
}else{
self.sizeLabel.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000))
}
let placeImage = self.mediaType == 0 ? "othermoren" : self.mediaType == 2 ? "photosmoren" : "videosmoren"
if let id = model.assets.first?.first?.localIdentifier{
imageView.asset.load(withLocalIdentifier:id,placeholder: UIImage(named: placeImage))
}else{
self.imageView.image = UIImage.init(named: placeImage)
}
}
......
......@@ -131,18 +131,26 @@ class HomeTitleCollectionCell:UICollectionViewCell {
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit()
collectionView?.reloadData()
}
func reloadCoverData(_ assets:[AssetModel]){
assetsModels.removeAll()
if let assets = model.assets.first{
assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model)
})
Print("刷新头部封面\(self)",assets.count)
collectionView?.reloadData()
}
}
// func reloadCoverData(_ assets:[AssetModel]){
// assetsModels.removeAll()
// assetsModels = assets.compactMap({ model in
// return ImageCollectionModel.init(asset: model)
// })
// Print("刷新头部封面\(self)",assets.count)
// collectionView?.reloadData()
// }
override func layoutSubviews() {
super.layoutSubviews()
......
......@@ -48,8 +48,6 @@ class ImageCollectionCell:UICollectionViewCell {
backImageView?.asset.load(withLocalIdentifier: model.asset.localIdentifier,placeholder: UIImage.init(named: "othermoren"))
}
}
......
......@@ -4,82 +4,119 @@ 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")
]
static let shared = HomeViewModel()
func getImage(index:Int) ->UIImage?{
return cardCoverImage.safeGet(index: index) ?? nil
}
private init(){}
func config(){}
func setImage(index:Int,image:UIImage?) {
guard let image = image else{
return
}
if cardCoverImage.count > index{
cardCoverImage[index] = image
}
}
}
let trash = TrashDatabase.shared.queryAll().compactMap{$0.localIdentifier}
let keep = GroupDatabase.shared.queryAll().compactMap{$0.localIdentifier}
// 封面图片资源缓存
// 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()
// let coverCache = CoverCacheActor()
// 相册资源总大小
var totalSize:Int64{
return videoSize + otherSize + screentSize
return photoManager.videoTotalSize + photoManager.otherTotalSize + photoManager.screenShotTotalSize //videoSize + otherSize + screentSize
}
var videoSize:Int64 = 0
var otherSize:Int64 = 0
var screentSize:Int64 = 0
// 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 headerGroup:[HomePhotosModel]{
return [
HomePhotosModel.init(
folderName: HomeUIEnum.Dublicates.title,
allFileSize:getTotalSize(source: photoManager.duplicateModels),
assets: photoManager.duplicateModels),
HomePhotosModel.init(
folderName: HomeUIEnum.Similar.title,
allFileSize:getTotalSize(source: photoManager.filterSimilarModels),
assets:photoManager.filterSimilarModels)
]
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: []),
}
// 首页UI数据结构
var cardGroup:[HomePhotosModel]{
return [
HomePhotosModel.init(
folderName: HomeUIEnum.Videos.title,
allFileSize:getTotalSize(source: [photoManager.filterVideoModels]),
assets: [photoManager.filterVideoModels]),
HomePhotosModel.init(
folderName: HomeUIEnum.SimilarScreenshots.title,
allFileSize: getTotalSize(source: photoManager.filterSimilarScreenShotModels),
assets: photoManager.filterSimilarScreenShotModels),
HomePhotosModel.init(
folderName: HomeUIEnum.Screensshots.title,
allFileSize:getTotalSize(source: [photoManager.filterScreenShotModels]),
assets:[photoManager.filterScreenShotModels]),
HomePhotosModel.init(
folderName: HomeUIEnum.SimilarVideos.title,
allFileSize:getTotalSize(source: photoManager.filterSimilarVideoModels),
assets: photoManager.filterSimilarVideoModels),
HomePhotosModel.init(
folderName: HomeUIEnum.Other.title,
allFileSize:getTotalSize(source: [photoManager.filterOtherModels]),
assets: [photoManager.filterOtherModels]),
]
}
func getTotalSize(source:[[AssetModel]]) ->Double{
return source.flatMap{$0}.reduce(0){$0+$1.assetSize}
}
var hadLoad = false
// 数据获取回调
var homeDataChanged:((_ section:Int,_ row:Int) ->Void)?
// 封面获取回调
var coverHadChange:(() ->Void)?
// 添加状态变化的回调闭包
var onLoadingStateChanged: ((BaseDataLoadingState) -> Void)?
var reloadCellHeight:(() ->Void)?
var dupCoverImage:[AssetModel] = []
var similarCoverImage:[AssetModel] = []
var headCoverImages:[[AssetModel]]{
return [
dupCoverImage,
similarCoverImage
]
}
// var dupCoverImage:[AssetModel] = []
//
// var similarCoverImage:[AssetModel] = []
//
// var headCoverImages:[[AssetModel]]{
// return [
// dupCoverImage,
// similarCoverImage
// ]
// }
func setupBindings() {
......@@ -106,9 +143,11 @@ class HomeViewModel {
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.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.filterResource()
weakSelf.homeDataChanged?(1,type.index)
}
......@@ -116,9 +155,10 @@ class HomeViewModel {
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.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.filterResource()
weakSelf.homeDataChanged?(1,type.index)
}
......@@ -126,9 +166,10 @@ class HomeViewModel {
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.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.filterResource()
weakSelf.homeDataChanged?(1,type.index)
}
startMainTask()
......@@ -136,39 +177,51 @@ class HomeViewModel {
func startMainTask(){
DispatchQueue.main.async {[weak self] in
guard let weakSelf = self else { return }
weakSelf.getSimilarOptimizer()
weakSelf.getSimilarScreenOptimizer()
weakSelf.getSimilarVideoOptimizer()
weakSelf.getGroupDuplicateImages()
}
getSimilarOptimizer()
getSimilarScreenOptimizer()
getSimilarVideoOptimizer()
getGroupDuplicateImages()
// DispatchQueue.main.async {[weak self] in
// guard let weakSelf = self else { return }
// weakSelf.getSimilarOptimizer()
// weakSelf.getSimilarScreenOptimizer()
// weakSelf.getSimilarVideoOptimizer()
// //weakSelf.getGroupDuplicateImages()
// }
}
// 获取相似图片
@MainActor func getSimilarOptimizer(){
func getSimilarOptimizer(){
let type = HomeUIEnum.Similar
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
// var currentGorup:[[AssetModel]] = []
// var currentSize:Double = 0
// var firstId:String?
var hadblock = false
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}
// 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.headerGroup[type.index].assets = currentGorup
// weakSelf.headerGroup[type.index].allFileSize = currentSize
weakSelf.photoManager.similarModels.append(group)
// if let id = weakSelf.photoManager.similarModels.first?.first?.localIdentifier{
// if firstId != id{
// firstId = id
// weakSelf.similarCoverImage = group
// weakSelf.coverHadChange?()
// }
// }
if !hadblock{
weakSelf.reloadCellHeight?()
hadblock = true
}
}
weakSelf.homeDataChanged?(0,type.index)
......@@ -176,145 +229,218 @@ class HomeViewModel {
guard let weakSelf = self else { return }
print("获取相似图片完成",totalGroup.count)
weakSelf.photoManager.similarModels = totalGroup
weakSelf.filterResource()
weakSelf.homeDataChanged?(0,type.index)
}
}
// 获取相似截图
@MainActor func getSimilarScreenOptimizer(){
func getSimilarScreenOptimizer(){
let type = HomeUIEnum.SimilarScreenshots
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
// 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}
// 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.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.photoManager.similarScreenShotModels.append(group)
weakSelf.homeDataChanged?(1,type.index)
} completionHandler: {[weak self] totalGroup in
guard let weakSelf = self else { return }
print("获取相似截图完成",totalGroup.count)
weakSelf.filterResource()
weakSelf.photoManager.similarScreenShotModels = totalGroup
}
}
@MainActor func getSimilarVideoOptimizer(){
func getSimilarVideoOptimizer(){
let type = HomeUIEnum.SimilarVideos
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
// 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
if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.getCoverImage(type: type, identifier: firstId)
}
}
// currentGorup.append(group)
// currentSize += group.reduce(0){$0+$1.assetSize}
//
// 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.photoManager.similarVideoModels.append(group)
weakSelf.homeDataChanged?(1,type.index)
} completionHandler: {[weak self] totalGroup in
print("获取相似视频完成",totalGroup.count)
guard let weakSelf = self else { return }
weakSelf.filterResource()
weakSelf.photoManager.similarVideoModels = totalGroup
}
}
// 获取重复图片
@MainActor func getGroupDuplicateImages(){
func getGroupDuplicateImages(){
let type = HomeUIEnum.Dublicates
var currentGorup:[[AssetModel]] = []
var currentSize:Double = 0
var firstId:String?
// var currentGorup:[[AssetModel]] = []
// var currentSize:Double = 0
// var firstId:String?
PhotoDuplicateManager.shared.findDuplicateAssets(in: photoManager.otherAssets, mediaType: .photo) {[weak self] groups in
guard let weakSelf = self else { return }
// let size = groups.map{$0}.reduce(into: 0){$0+$1.assetSize}
weakSelf.headerGroup[type.index].assets = groups
weakSelf.headerGroup[type.index].allFileSize = currentSize
// weakSelf.headerGroup[type.index].assets = groups
// weakSelf.headerGroup[type.index].allFileSize = currentSize
weakSelf.photoManager.duplicateModels = groups
// if let id = groups.first?.first?.localIdentifier{
// if firstId != id{
// firstId = id
// weakSelf.dupCoverImage = groups.first ?? []
// weakSelf.coverHadChange?()
// }
// }
let currentThread = Thread.current
if currentThread.isMainThread {
print("在主线程执行")
} else {
print("在后台线程执行")
}
weakSelf.reloadCellHeight?()
weakSelf.homeDataChanged?(0,type.index)
} progressHandler: {group in
// guard let weakSelf = self else { return }
// currentGorup.append(group)
// currentSize += group.reduce(0){$0+$1.assetSize}
//
// weakSelf.headerGroup[type.index].assets = currentGorup
// weakSelf.headerGroup[type.index].allFileSize = currentSize
// weakSelf.photoManager.duplicateModels = groups
// 从 group 中过滤掉存在于 duplicateModels 中的元素
// let filteredGroup = group.filter { item in
// !weakSelf.photoManager.duplicateModels.flatMap { $0 }.contains(item)
// }
//
//
// if let id = currentGorup.first?.first?.localIdentifier{
// if firstId != id{
// firstId = id
// weakSelf.dupCoverImage = group
// weakSelf.coverHadChange?()
// }
// }
//
// weakSelf.homeDataChanged?(0,type.index)
if let id = groups.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.dupCoverImage = groups.first ?? []
weakSelf.coverHadChange?()
} completionHandler: {[weak self] totalGroup in
guard let weakSelf = self else { return }
print("获得重复图片完成",totalGroup.count)
let currentThread = Thread.current
if currentThread.isMainThread {
print("在主线程执行")
} else {
print("在后台线程执行")
}
weakSelf.photoManager.duplicateModels = totalGroup
weakSelf.reloadCellHeight?()
weakSelf.homeDataChanged?(0,type.index)
}
}
weakSelf.homeDataChanged?(0,type.index)
// 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?()
// }
// }
// }
// })
// }
// }
} progressHandler: {[weak self] group in
guard let weakSelf = self else { return }
}
currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize}
extension HomeViewModel{
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?()
}
}
// 基本资源过滤保留和垃圾桶资源
func filterResource(){
weakSelf.homeDataChanged?(0,type.index)
let filterArray = trash + keep
} completionHandler: {[weak self] totalGroup in
guard let weakSelf = self else { return }
weakSelf.photoManager.duplicateModels = totalGroup
}
}
let others = removeAssets(withIdentifiers: filterArray, from: photoManager.otherModels)
let videos = removeAssets(withIdentifiers: filterArray, from: photoManager.videoModels)
let screens = removeAssets(withIdentifiers: filterArray, from: photoManager.screenShotModels)
photoManager.filterOtherModels = others
photoManager.filterVideoModels = videos
photoManager.filterScreenShotModels = screens
let similarPhotos = filterGroups(photoManager.similarModels, byExcludingIDs: filterArray)
let similarVideos = filterGroups(photoManager.similarVideoModels, byExcludingIDs: filterArray)
let similarShots = filterGroups(photoManager.similarScreenShotModels, byExcludingIDs: filterArray)
photoManager.filterSimilarModels = similarPhotos
photoManager.filterSimilarVideoModels = similarVideos
photoManager.filterSimilarScreenShotModels = similarShots
reloadCellHeight?()
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?()
}
}
func removeAssets(withIdentifiers identifiers: [String], from assets: [AssetModel]) -> [AssetModel] {
let identifierSet = Set(identifiers)
return assets.filter { !identifierSet.contains($0.localIdentifier) }
}
})
func filterGroups(_ groups: [[AssetModel]], byExcludingIDs ids: [String]) -> [[AssetModel]] {
let excludeSet = Set(ids)
return groups.filter { group in
// 检查子数组中是否所有元素的ID都不在排除列表中
group.allSatisfy { !excludeSet.contains($0.localIdentifier) }
}
}
......
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