Commit a5cdb8eb authored by yqz's avatar yqz

Merge branch 'dev_main' into yQz0507

* dev_main:
  【优化】优化左右滑动功能
  处理退款,修复保留bug,首页视频播放
  优化图片加载器
  订阅增加类型获取,新增年费会员
  年费会员界面,启动引导页结构更改

# Conflicts:
#	PhoneManager/Class/Page/Home/Controller/HomeViewController.swift
parents ed8f8528 9febb62f
...@@ -27,13 +27,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -27,13 +27,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.backgroundColor = .white window?.backgroundColor = .white
window?.overrideUserInterfaceStyle = .light window?.overrideUserInterfaceStyle = .light
let Ssoryboard = UIStoryboard(name: "LauchVC", bundle: nil)
if let current = Ssoryboard.instantiateViewController(identifier: "LauchVCID") as? LauchVC { let rootNav = BaseNavViewController(rootViewController: HomeViewController())
window?.rootViewController = rootNav
window?.rootViewController = current window?.makeKeyAndVisible()
window?.makeKeyAndVisible()
} // let Ssoryboard = UIStoryboard(name: "LauchVC", bundle: nil)
//
// if let current = Ssoryboard.instantiateViewController(identifier: "LauchVCID") as? LauchVC {
//
// window?.rootViewController = current
// window?.makeKeyAndVisible()
// }
let battery = WidgetPublicModel.battery() let battery = WidgetPublicModel.battery()
let storage = WidgetPublicModel.UseDiskSpace() * 100 let storage = WidgetPublicModel.UseDiskSpace() * 100
widgetAppgourp.share.PushWidgetData(battery: Int(battery), storage: Int(storage)) widgetAppgourp.share.PushWidgetData(battery: Int(battery), storage: Int(storage))
...@@ -52,6 +58,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -52,6 +58,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// 相册基本资源加载 // 相册基本资源加载
PhotoManager.shared.config() PhotoManager.shared.config()
// 设置app动态按钮
setupDynamicShortcuts()
return true return true
} }
...@@ -128,20 +136,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -128,20 +136,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private func setupDefault() { private func setupDefault() {
IAPManager.share.checkSubscriptionState { isSubscribed, expiresDate in
Print("是否内购---",isSubscribed)
}
NetStatusManager.manager.startNet { status in NetStatusManager.manager.startNet { status in
switch status { switch status {
case .NoNet: case .NoNet:
break break
case .WIFI,.WWAN: case .WIFI,.WWAN:
break
if (PhotoAndVideoMananger.mananger.allAssets.count == 0) {
PhotoAndVideoMananger.mananger.setAssets()
}
} }
} }
......
//
// AppDelegateEx.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import Foundation
import UIKit
extension AppDelegate{
func setupDynamicShortcuts() {
let shortcutItem = UIApplicationShortcutItem(
type: "com.app.phonemanager.iap.distance",
localizedTitle: "Unlock Exclusive Discounts",
localizedSubtitle: "Your special offer is awaiting—don't miss this limited-time second chance benefit!",
icon: UIApplicationShortcutIcon(systemImageName: "star.fill"),
userInfo: nil
)
UIApplication.shared.shortcutItems = [shortcutItem]
}
// 处理快捷方式点击
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
// 处理快捷方式点击事件
switch shortcutItem.type {
case "com.app.phonemanager.iap.distance":
// 执行相应操作
IAPManager.share.showYearPage = true
if IAPManager.share.isHotLaunch{
// 热启动走这里
switch IAPManager.share.subscriptionType {
case .none,.week:
let vc:PayDistanceViewController = PayDistanceViewController()
let nav:BaseNavViewController = BaseNavViewController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
guard let rt = UIViewController.topMostViewController() else { return }
rt.present(nav, animated: true)
default:
let vc : PayCompletedViewController = PayCompletedViewController()
guard let rt = UIViewController.topMostViewController() else { return }
vc.modalPresentationStyle = .fullScreen
rt.present(vc, animated: true)
}
}
completionHandler(true)
default:
completionHandler(false)
}
}
}
{
"images" : [
{
"filename" : "Frame_1171276231.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame_1171276231@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame_1171276231@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Frame_1171276231.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame_1171276231@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame_1171276231@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Group_1171275102.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275102@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275102@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Group_1171275102.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275102@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275102@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Group_1171275102.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275102@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275102@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Group_1171275242.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275242@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275242@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "Group_1171275243.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275243@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275243@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"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
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame_1171276222@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame_1171276222@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -37,6 +37,15 @@ enum IAPError: Error { ...@@ -37,6 +37,15 @@ enum IAPError: Error {
} }
} }
// 添加一个枚举来表示订阅类型
enum SubscriptionType {
case none
case week
case year
case lifetime
}
// MARK: - IAPManager // MARK: - IAPManager
class IAPManager: NSObject { class IAPManager: NSObject {
...@@ -48,7 +57,8 @@ class IAPManager: NSObject { ...@@ -48,7 +57,8 @@ class IAPManager: NSObject {
private let alert = PMLoadingHUD.share private let alert = PMLoadingHUD.share
enum PayState { enum PayState {
case subscribe case weekSubscribe
case yearSubscribe
case nonConsumable case nonConsumable
} }
...@@ -56,9 +66,10 @@ class IAPManager: NSObject { ...@@ -56,9 +66,10 @@ class IAPManager: NSObject {
private struct ProductID { private struct ProductID {
static let weekMember = "com.app.phonemanager.week.member" static let weekMember = "com.app.phonemanager.week.member"
static let lifetimeMember = "com.app.phonemanager.lifetime.member" static let lifetimeMember = "com.app.phonemanager.lifetime.member"
static let yearMember = "com.app.phonemanager.year.member"
static var all: [String] { static var all: [String] {
return [weekMember, lifetimeMember] return [weekMember, lifetimeMember,yearMember]
} }
} }
...@@ -66,14 +77,16 @@ class IAPManager: NSObject { ...@@ -66,14 +77,16 @@ class IAPManager: NSObject {
private var weekProduct: SKProduct? private var weekProduct: SKProduct?
// 永久订阅 // 永久订阅
private var lifetimeProduct: SKProduct? private var lifetimeProduct: SKProduct?
// 年费订阅
private var yearProduct: SKProduct?
private var state: PayState = .subscribe private var state: PayState = .weekSubscribe
//购买操作的完成回调 //购买操作的完成回调
private var purchaseCompletion: ((Result<Bool, IAPError>) -> Void)? private var purchaseCompletion: ((Result<Bool, IAPError>) -> Void)?
//恢复购买的完成回调 //恢复购买的完成回调
private var restoreCompletion: ((Result<Bool, IAPError>) -> Void)? private var restoreCompletion: ((Result<Bool, IAPError>) -> Void)?
//获取商品信息的完成回调 //获取商品信息的完成回调
private var productRequestCompletion: (((SKProduct?,SKProduct?)?) -> Void)? private var productRequestCompletion: (((SKProduct?,SKProduct?,SKProduct?)?) -> Void)?
var isSubscribed = false { var isSubscribed = false {
didSet { didSet {
...@@ -83,10 +96,23 @@ class IAPManager: NSObject { ...@@ -83,10 +96,23 @@ class IAPManager: NSObject {
} }
} }
// 是否展示优惠订阅
var showYearPage:Bool = false
// 是否热启动
var isHotLaunch:Bool = false
// 订阅状态
var subscriptionType:SubscriptionType = .none{
didSet{
isSubscribed = subscriptionType != .none
}
}
// MARK: - Initialization // MARK: - Initialization
private override init() { private override init() {
super.init() super.init()
SKPaymentQueue.default().add(self) SKPaymentQueue.default().add(self)
} }
deinit { deinit {
...@@ -98,15 +124,16 @@ class IAPManager: NSObject { ...@@ -98,15 +124,16 @@ class IAPManager: NSObject {
extension IAPManager { extension IAPManager {
// 获取订阅内购商品信息 // 获取订阅内购商品信息
func fetchProducts(completion: @escaping ((SKProduct?,SKProduct?)?) -> Void) { func fetchProducts(completion: @escaping ((SKProduct?,SKProduct?,SKProduct?)?) -> Void) {
// 先检查缓存 // 先检查缓存
if let cachedProducts = getCachedProducts() { if let cachedProducts = getCachedProducts() {
// 后台更新最新数据 // 后台更新最新数据
self.weekProduct = cachedProducts.filter{$0.productIdentifier == ProductID.weekMember}.first self.weekProduct = cachedProducts.filter{$0.productIdentifier == ProductID.weekMember}.first
self.lifetimeProduct = cachedProducts.filter{$0.productIdentifier == ProductID.lifetimeMember}.first self.lifetimeProduct = cachedProducts.filter{$0.productIdentifier == ProductID.lifetimeMember}.first
completion((self.weekProduct,self.lifetimeProduct)) self.yearProduct = cachedProducts.filter{$0.productIdentifier == ProductID.yearMember}.first
completion((self.weekProduct,self.lifetimeProduct,self.yearProduct))
refreshProducts() refreshProducts()
return
} }
productRequestCompletion = completion productRequestCompletion = completion
...@@ -128,38 +155,69 @@ extension IAPManager { ...@@ -128,38 +155,69 @@ extension IAPManager {
// 检查订阅信息 // 检查订阅信息
/// - Parameter completion: 回调闭包,返回订阅状态和到期时间 /// - Parameter completion: 回调闭包,返回订阅状态和到期时间
func checkSubscriptionState(completion: @escaping (_ isSubscribed: Bool, _ expiresDate: Date?) -> Void) { // func checkSubscriptionState(completion: @escaping (_ isSubscribed: Bool, _ expiresDate: Date?) -> Void) {
// verifyReceiptWithApple { result in
// switch result {
// case .success(let receipt):
// // 打印完整收据信息,方便调试
// //print("收据信息:\(receipt)")
// let status = self.checkSubscriptionStatus(receiptInfo: receipt)
// DispatchQueue.main.async {
// completion(status.isActive, status.expiresDate)
// // 更新本地订阅状态
// self.isSubscribed = status.isActive
// #if DEBUG
// if let expDate = status.expiresDate {
// let formatter = DateFormatter()
// formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
// Print("订阅状态:\(status.isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))")
// }
// #endif
// }
//
// case .failure(_):
// // 处理错误情况
// self.refreshReceipt()
// completion(false, nil)
// }
// }
// }
// 修改回调方法,增加订阅类型参数
func checkSubscriptionState(completion: @escaping (_ isSubscribed: Bool, _ subscriptionType: SubscriptionType, _ expiresDate: Date?) -> Void) {
verifyReceiptWithApple { result in verifyReceiptWithApple { result in
switch result { switch result {
case .success(let receipt): case .success(let receipt):
// 打印完整收据信息,方便调试 // print("收据信息:\(receipt)")
//print("收据信息:\(receipt)")
let status = self.checkSubscriptionStatus(receiptInfo: receipt) let status = self.checkSubscriptionStatus(receiptInfo: receipt)
DispatchQueue.main.async { DispatchQueue.main.async {
completion(status.isActive, status.expiresDate) completion(status.isActive, status.subscriptionType, status.expiresDate)
// 更新本地订阅状态
self.isSubscribed = status.isActive self.isSubscribed = status.isActive
self.subscriptionType = status.subscriptionType
#if DEBUG #if DEBUG
if let expDate = status.expiresDate { if let expDate = status.expiresDate {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
Print("订阅状态:\(status.isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))") Print("订阅状态:\(status.isActive ? "已订阅" : "未订阅")订阅类型:\(status.subscriptionType)到期时间:\(formatter.string(from: expDate))")
} }
#endif #endif
} }
case .failure(_): case .failure(_):
// 处理错误情况
self.refreshReceipt() self.refreshReceipt()
completion(false, nil) completion(false, .none, nil)
} }
} }
} }
func purchase(_ state: PayState = .subscribe, completion: @escaping (Result<Bool, IAPError>) -> Void) { func purchase(_ state: PayState = .weekSubscribe, completion: @escaping (Result<Bool, IAPError>) -> Void) {
if state == .subscribe,weekProduct == nil{ if state == .weekSubscribe,weekProduct == nil{
completion(.failure(.noProductsFound))
return
}
if state == .yearSubscribe,yearProduct == nil{
completion(.failure(.noProductsFound)) completion(.failure(.noProductsFound))
return return
} }
...@@ -175,12 +233,23 @@ extension IAPManager { ...@@ -175,12 +233,23 @@ extension IAPManager {
self.state = state self.state = state
self.purchaseCompletion = completion self.purchaseCompletion = completion
let payment = SKPayment(product: state == .subscribe ? weekProduct! : lifetimeProduct!)
var payment:SKPayment = SKPayment()
switch state{
case .weekSubscribe:
payment = SKPayment(product: weekProduct!)
case .yearSubscribe:
payment = SKPayment(product: yearProduct!)
case .nonConsumable:
payment = SKPayment(product: lifetimeProduct!)
}
SKPaymentQueue.default().add(payment) SKPaymentQueue.default().add(payment)
alert.show("Processing the purchase request...", "") alert.show("Processing the purchase request...", "")
} }
func restore(_ state: PayState = .subscribe, completion: @escaping (Result<Bool, IAPError>) -> Void) { func restore(_ state: PayState = .weekSubscribe, completion: @escaping (Result<Bool, IAPError>) -> Void) {
self.state = state self.state = state
self.restoreCompletion = completion self.restoreCompletion = completion
SKPaymentQueue.default().restoreCompletedTransactions() SKPaymentQueue.default().restoreCompletedTransactions()
...@@ -384,15 +453,15 @@ extension IAPManager: SKProductsRequestDelegate { ...@@ -384,15 +453,15 @@ extension IAPManager: SKProductsRequestDelegate {
// 拿到请求下来的商品信息 // 拿到请求下来的商品信息
self.weekProduct = products.filter{$0.productIdentifier == ProductID.weekMember}.first self.weekProduct = products.filter{$0.productIdentifier == ProductID.weekMember}.first
self.lifetimeProduct = products.filter{$0.productIdentifier == ProductID.lifetimeMember}.first self.lifetimeProduct = products.filter{$0.productIdentifier == ProductID.lifetimeMember}.first
self.yearProduct = products.filter{$0.productIdentifier == ProductID.yearMember}.first
// 缓存 // 缓存
if let week = self.weekProduct,let life = self.lifetimeProduct{ if let week = self.weekProduct,let life = self.lifetimeProduct,let year = self.yearProduct{
cacheProducts([week,life]) cacheProducts([week,life,year])
} }
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
self?.productRequestCompletion?((weakSelf.weekProduct,weakSelf.lifetimeProduct)) self?.productRequestCompletion?((weakSelf.weekProduct,weakSelf.lifetimeProduct,weakSelf.yearProduct))
BackgroundTaskManager.share.endTask() BackgroundTaskManager.share.endTask()
} }
} }
...@@ -419,7 +488,7 @@ extension IAPManager: SKPaymentTransactionObserver { ...@@ -419,7 +488,7 @@ extension IAPManager: SKPaymentTransactionObserver {
/** 恢复购买 */ /** 恢复购买 */
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
// 刷新订阅状态 // 刷新订阅状态
checkSubscriptionState { [weak self] isSubscribed, expiresDate in checkSubscriptionState { [weak self] isSubscribed,type,expiresDate in
guard let self = self else { return } guard let self = self else { return }
// 更新恢复购买的结果 // 更新恢复购买的结果
if isSubscribed { if isSubscribed {
...@@ -447,55 +516,118 @@ extension IAPManager: SKPaymentTransactionObserver { ...@@ -447,55 +516,118 @@ extension IAPManager: SKPaymentTransactionObserver {
extension IAPManager{ extension IAPManager{
func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, expiresDate: Date?) { // func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, expiresDate: Date?) {
// 首先检查 in_app 购买记录 // // 首先检查 in_app 购买记录
if let receipt = receiptInfo["receipt"] as? [String: Any], // if let receipt = receiptInfo["receipt"] as? [String: Any],
let inAppPurchases = receipt["in_app"] as? [[String: Any]] { // let inAppPurchases = receipt["in_app"] as? [[String: Any]] {
// 检查是否存在永久商品购买记录 // // 检查是否存在永久商品购买记录
let hasLifetimePurchase = inAppPurchases.contains { purchase in // let hasLifetimePurchase = inAppPurchases.contains { purchase in
return purchase["product_id"] as? String == ProductID.lifetimeMember // return purchase["product_id"] as? String == ProductID.lifetimeMember
} // }
//
if hasLifetimePurchase { // if hasLifetimePurchase {
print("检测到永久商品购买记录") // print("检测到永久商品购买记录")
return (true, false, nil) // return (true, false, nil)
} // }
} // }
//
// 如果没有永久商品,继续检查订阅 // // 如果没有永久商品,继续检查订阅
guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else { // guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else {
print("没有找到任何购买记录") // print("没有找到任何购买记录")
return (false, false, nil) // return (false, false, nil)
} // }
//
// 按过期时间排序,确保获取最新的订阅记录 // // 按过期时间排序,确保获取最新的订阅记录
let sortedReceipts = latestReceiptInfo.sorted { receipt1, receipt2 in // let sortedReceipts = latestReceiptInfo.sorted { receipt1, receipt2 in
let date1 = getExpiryDate(from: receipt1) ?? Date.distantPast // let date1 = getExpiryDate(from: receipt1) ?? Date.distantPast
let date2 = getExpiryDate(from: receipt2) ?? Date.distantPast // let date2 = getExpiryDate(from: receipt2) ?? Date.distantPast
return date1 > date2 // return date1 > date2
} // }
//
guard let lastReceipt = sortedReceipts.first else { // guard let lastReceipt = sortedReceipts.first else {
return (false, false, nil) // return (false, false, nil)
} // }
//
// 解析试用期状态 // // 解析试用期状态
let isTrial = (lastReceipt["is_trial_period"] as? String) == "true" // let isTrial = (lastReceipt["is_trial_period"] as? String) == "true"
//
// 获取过期时间 // // 获取过期时间
let expiresDate = getExpiryDate(from: lastReceipt) // let expiresDate = getExpiryDate(from: lastReceipt)
let isActive = expiresDate?.compare(Date()) == .orderedDescending // let isActive = expiresDate?.compare(Date()) == .orderedDescending
//
#if DEBUG // #if DEBUG
if let expDate = expiresDate { // if let expDate = expiresDate {
let formatter = DateFormatter() // let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print("订阅状态:\(isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))") // print("订阅状态:\(isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))")
} // }
#endif // #endif
//
return (isActive, isTrial, expiresDate) // return (isActive, isTrial, expiresDate)
} // }
func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, subscriptionType: SubscriptionType, expiresDate: Date?) {
// 首先检查 in_app 购买记录
if let receipt = receiptInfo["receipt"] as? [String: Any],
let inAppPurchases = receipt["in_app"] as? [[String: Any]] {
// 检查是否存在永久商品购买记录
let hasValidLifetimePurchase = inAppPurchases.contains { purchase in
let productId = purchase["product_id"] as? String
let cancelled = purchase["cancellation_date"] != nil
let ownershipType = purchase["in_app_ownership_type"] as? String
return productId == ProductID.lifetimeMember &&
!cancelled &&
ownershipType == "PURCHASED"
}
if hasValidLifetimePurchase {
print("检测到有效的永久商品购买记录")
return (true, false, .lifetime, nil)
}
}
// 如果没有永久商品,继续检查订阅
guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else {
print("没有找到任何购买记录")
return (false, false, .none, nil)
}
// 按过期时间排序,确保获取最新的订阅记录
let sortedReceipts = latestReceiptInfo.sorted { receipt1, receipt2 in
let date1 = getExpiryDate(from: receipt1) ?? Date.distantPast
let date2 = getExpiryDate(from: receipt2) ?? Date.distantPast
return date1 > date2
}
guard let lastReceipt = sortedReceipts.first else {
return (false, false, .none, nil)
}
// 解析试用期状态
let isTrial = (lastReceipt["is_trial_period"] as? String) == "true"
// 获取过期时间和退款状态
let expiresDate = getExpiryDate(from: lastReceipt)
let isCancelled = lastReceipt["cancellation_date"] != nil
let isActive = !isCancelled && (expiresDate?.compare(Date()) == .orderedDescending)
// 确定订阅类型
let productId = lastReceipt["product_id"] as? String
let subscriptionType: SubscriptionType
switch productId {
case ProductID.weekMember:
subscriptionType = .week
case ProductID.yearMember:
subscriptionType = .year
case ProductID.lifetimeMember:
subscriptionType = .lifetime
default:
subscriptionType = .none
}
return (isActive, isTrial, subscriptionType, expiresDate)
}
// 辅助方法:解析过期时间 // 辅助方法:解析过期时间
private func getExpiryDate(from receipt: [String: Any]) -> Date? { private func getExpiryDate(from receipt: [String: Any]) -> Date? {
......
...@@ -2,7 +2,12 @@ import UIKit ...@@ -2,7 +2,12 @@ import UIKit
import Photos import Photos
// 图片缓存管理(单例) // 图片缓存管理(单例)
private let imageCache = NSCache<NSString, UIImage>() private let imageCache: NSCache<NSString, UIImage> = {
let cache = NSCache<NSString, UIImage>()
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
cache.countLimit = 100 // 最多缓存100张图片
return cache
}()
extension UIImageView { extension UIImageView {
// 关联对象键,用于存储当前请求的 localIdentifier // 关联对象键,用于存储当前请求的 localIdentifier
...@@ -34,7 +39,7 @@ extension UIImageView { ...@@ -34,7 +39,7 @@ extension UIImageView {
// 加载图片的主方法 // 加载图片的主方法
func loadImage( func loadImage(
withLocalIdentifier identifier: String, withLocalIdentifier identifier: String,
targetSize: CGSize = PHImageManagerMaximumSize, targetSize: CGSize = CGSize(width: 300, height: 300),
placeholder: UIImage? = nil, placeholder: UIImage? = nil,
completion: ((UIImage?) -> Void)? = nil completion: ((UIImage?) -> Void)? = nil
) { ) {
...@@ -63,13 +68,23 @@ extension UIImageView { ...@@ -63,13 +68,23 @@ extension UIImageView {
completion?(nil) completion?(nil)
return return
} }
// 6. 配置图片请求选项 // 6. 配置图片请求选项
let options = PHImageRequestOptions() let options = PHImageRequestOptions()
options.version = .current options.version = .current
options.deliveryMode = .highQualityFormat options.deliveryMode = .highQualityFormat // 改为渐进式加载
options.isNetworkAccessAllowed = true options.isNetworkAccessAllowed = true
options.resizeMode = .exact options.resizeMode = .fast // 改为快速加载模式
options.isSynchronous = false
options.progressHandler = { progress, error, stop, info in
DispatchQueue.main.async {
// 可以在这里添加加载进度的处理
// print("Loading progress: \(progress)")
if let error = error {
print("Loading error: \(error.localizedDescription)")
}
}
}
// 7. 请求图片 // 7. 请求图片
currentRequestID = PHImageManager.default().requestImage( currentRequestID = PHImageManager.default().requestImage(
...@@ -116,9 +131,18 @@ extension UIImageView { ...@@ -116,9 +131,18 @@ extension UIImageView {
} }
} }
// 清理缓存
@objc func clearCacheOnMemoryWarning() {
imageCache.removeAllObjects()
}
// 命名空间 // 命名空间
struct AssetLoader { struct AssetLoader {
fileprivate weak var imageView: UIImageView? fileprivate weak var imageView: UIImageView?
func clearImageCache() {
imageCache.removeAllObjects()
}
} }
var asset: AssetLoader { var asset: AssetLoader {
......
...@@ -34,6 +34,12 @@ enum BaseDataLoadingState { ...@@ -34,6 +34,12 @@ enum BaseDataLoadingState {
case failed(Error) case failed(Error)
} }
enum FileType {
case json
case plist
case all
}
class PhotoManager{ class PhotoManager{
...@@ -137,9 +143,14 @@ class PhotoManager{ ...@@ -137,9 +143,14 @@ class PhotoManager{
private func requestAuthorization(){ private func requestAuthorization(){
// 获取基础数据 // 获取基础数据
requestAuthorization {[weak self] _ in requestAuthorization {[weak self] permission in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
weakSelf.getBaseAssetGroup() if permission{
weakSelf.getBaseAssetGroup()
}else{
//weakSelf.clearLocalData()
}
} }
} }
...@@ -204,6 +215,7 @@ class PhotoManager{ ...@@ -204,6 +215,7 @@ class PhotoManager{
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2) self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2)
self.videoModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("视频转换总耗时: \(duration)秒") print("视频转换总耗时: \(duration)秒")
let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize}) let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize})
...@@ -233,6 +245,7 @@ class PhotoManager{ ...@@ -233,6 +245,7 @@ class PhotoManager{
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1) self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1)
self.otherModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒") print("其他图片转换总耗时: \(duration)秒")
let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize}) let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize})
...@@ -258,11 +271,11 @@ class PhotoManager{ ...@@ -258,11 +271,11 @@ class PhotoManager{
self.screenShotTotalSize = screenShotTotalSize self.screenShotTotalSize = screenShotTotalSize
complectionHandler?(localModels, screenShotTotalSize) complectionHandler?(localModels, screenShotTotalSize)
} }
return
} }
let start = CFAbsoluteTimeGetCurrent() let start = CFAbsoluteTimeGetCurrent()
self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1) self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1)
self.screenShotModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start let duration = CFAbsoluteTimeGetCurrent() - start
print("截图转换总耗时: \(duration)秒") print("截图转换总耗时: \(duration)秒")
let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize}) let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize})
...@@ -513,7 +526,7 @@ extension PhotoManager{ ...@@ -513,7 +526,7 @@ extension PhotoManager{
options.version = .original options.version = .original
options.deliveryMode = .highQualityFormat options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true options.isNetworkAccessAllowed = true
// 4. 请求视频资源 // 4. 请求视频资源
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, _, _) in PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, _, _) in
DispatchQueue.main.async { DispatchQueue.main.async {
...@@ -748,6 +761,11 @@ extension PhotoManager{ ...@@ -748,6 +761,11 @@ extension PhotoManager{
} }
} }
// 清楚本地存储数据
func clearLocalData(){
}
func removeAssets(withIdentifiers identifiers: [String], from assets: [AssetModel]) -> [AssetModel] { func removeAssets(withIdentifiers identifiers: [String], from assets: [AssetModel]) -> [AssetModel] {
let identifierSet = Set(identifiers) let identifierSet = Set(identifiers)
......
...@@ -14,8 +14,12 @@ class HomeViewController:BaseViewController { ...@@ -14,8 +14,12 @@ class HomeViewController:BaseViewController {
private var isShowCharge:Bool = false private var isShowCharge:Bool = false
private var canShowIAP:Bool = false
var homeView:HomeView? var homeView:HomeView?
fileprivate func junmToModule(_ cIndex: Int, _ self: HomeViewController) { fileprivate func junmToModule(_ cIndex: Int, _ self: HomeViewController) {
switch cIndex { switch cIndex {
...@@ -107,7 +111,7 @@ class HomeViewController:BaseViewController { ...@@ -107,7 +111,7 @@ class HomeViewController:BaseViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
loadLaunchView()
self.setupUI() self.setupUI()
// 调用下追踪权限 // 调用下追踪权限
...@@ -181,10 +185,73 @@ class HomeViewController:BaseViewController { ...@@ -181,10 +185,73 @@ class HomeViewController:BaseViewController {
view.addSubview(homeView!) view.addSubview(homeView!)
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
barHidden = false barHidden = false
} }
func loadLaunchView(){
let luanch = HomeLaunchView(frame: CGRect(x: 0, y: 0, width: ScreenW, height: ScreenH))
luanch.show()
luanch.disMissBlock = {[weak self] in
guard let weakSelf = self else { return }
guard UserDef.shard.isShowLanding else{
return
}
weakSelf.showIAPVC()
}
if !UserDef.shard.isShowLanding{
let Ssoryboard = UIStoryboard(name: "PermissionVC", bundle: nil)
if let current = Ssoryboard.instantiateViewController(identifier: "PermissionVCID") as? PermissionVC {
self.navigationController?.pushViewController(current, animated: false)
}
}
NotificationCenter.default.addObserver(forName: .guidePageClose, object: nil, queue: nil) {[weak self] _ in
guard let weakSelf = self else { return }
weakSelf.showIAPVC()
}
}
func showIAPVC(){
IAPManager.share.isHotLaunch = true
IAPManager.share.checkSubscriptionState { isSubscribed,type,expiresDate in
Print("是否内购---",isSubscribed)
if IAPManager.share.showYearPage{
//按钮启动
switch type {
case .none,.week:
let vc:PayDistanceViewController = PayDistanceViewController()
let nav:BaseNavViewController = BaseNavViewController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true)
case .lifetime,.year:
let vc : PayCompletedViewController = PayCompletedViewController()
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true)
}
}else{
// 正常启动
if !isSubscribed{
DispatchQueue.main.async {
if !IAPManager.share.showYearPage{
HomePayViewController.show {
NotificationManager().configNotifications()
}
}
}
}
}
}
}
>>>>>>> dev_main
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
...@@ -204,6 +271,21 @@ class HomeViewController:BaseViewController { ...@@ -204,6 +271,21 @@ class HomeViewController:BaseViewController {
homeView?.viewModel.reloadTrashAndKeep() homeView?.viewModel.reloadTrashAndKeep()
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.barHidden = false
// 开启定时器
Singleton.shared.startCountdown {}
if !isShowCharge {
NotificationManager().configNotifications()
return
}
}
} }
extension HomeViewController { extension HomeViewController {
...@@ -260,3 +342,4 @@ extension HomeViewController { ...@@ -260,3 +342,4 @@ extension HomeViewController {
} }
} }
} }
...@@ -9,6 +9,11 @@ import UIKit ...@@ -9,6 +9,11 @@ import UIKit
import SnapKit import SnapKit
import Photos import Photos
enum SlideDirection {
case left
case right
}
class PhotoRemoveViewController: BaseViewController { class PhotoRemoveViewController: BaseViewController {
var mediaType : TrashTypeEnum? { var mediaType : TrashTypeEnum? {
...@@ -43,6 +48,23 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -43,6 +48,23 @@ class PhotoRemoveViewController: BaseViewController {
private let scaleMax: CGFloat = 0.7 private let scaleMax: CGFloat = 0.7
private let actionMargin: CGFloat = 120 private let actionMargin: CGFloat = 120
// 创建两个按钮
private lazy var trashButton = {
let button = UIButton(frame: CGRect(x: self.view.centerX - 22 - 47, y: self.view.centerY + 509 * RScreenH() / 2 - 47 / 2, width: 47, height: 47))
button.setImage(UIImage(named: "Group_1171275243"), for: .normal)
button.setImage(UIImage(named: "Group_1171275242"), for: .selected)
button.addTarget(self, action: #selector(trashButtonAction), for: .touchUpInside)
return button
}()
private lazy var keepListButton = {
let button = UIButton(frame: CGRect(x: self.view.centerX + 22, y: self.view.centerY + 509 * RScreenH() / 2 - 47 / 2, width: 47, height: 47))
button.setImage(UIImage(named: "Group_1171275102 3"), for: .normal)
button.setImage(UIImage(named: "Group_1171275102 2"), for: .selected)
button.addTarget(self, action: #selector(keepListButtonAction), for: .touchUpInside)
return button
}()
lazy var trashSubView : TrashSubView = { lazy var trashSubView : TrashSubView = {
let view = TrashSubView() let view = TrashSubView()
...@@ -68,11 +90,64 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -68,11 +90,64 @@ class PhotoRemoveViewController: BaseViewController {
self.currentIndex = tempArray.count self.currentIndex = tempArray.count
self.dataModel = result self.dataModel = result
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
// MARK: - 按钮事件
@objc func trashButtonAction(){
if self.photoViews.count > 0 {
if let view = self.photoViews.first {
self.animateViewExit(view: view,direction: .left)
}
}
}
@objc func keepListButtonAction(){
if self.photoViews.count > 0 {
if let view = self.photoViews.first {
self.animateViewExit(view: view,direction: .right)
}
}
}
func animateViewExit(view : PhotosRemoveBaseView, direction : SlideDirection) {
// 1. 设置锚点
let customAnchor = CGPoint(x: 0.5, y: 2.0)
view.layer.anchorPoint = customAnchor
// 2. 修正位置
let originalCenter = view.center
let offsetY = view.bounds.height * (customAnchor.y - 0.5)
view.layer.position = CGPoint(
x: originalCenter.x,
y: originalCenter.y + offsetY
)
// 3. 执行动画
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
view.transform = CGAffineTransform(rotationAngle: direction == SlideDirection.left ? -.pi/6 : .pi/6)
view.alpha = 0
}) { _ in
// 数据变化
if direction == SlideDirection.left{
// 删除操作,先存到单利
self.vibrate()
self.saveDataToDBAndSigtonTrash()
}else {
// 清除单利数据
self.clearSigtonTrashData()
// 保留操作
self.vibrate()
self.saveDataToSigtonKeepList()
}
// 视图变化
self.recycleView()
}
}
// MARK: - 生命周期 // MARK: - 生命周期
override func viewDidLoad() { override func viewDidLoad() {
...@@ -148,6 +223,10 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -148,6 +223,10 @@ class PhotoRemoveViewController: BaseViewController {
showCurrentPageUIWhenTashDataChanged() showCurrentPageUIWhenTashDataChanged()
self.addListener() self.addListener()
self.view.addSubview(self.trashButton)
self.view.addSubview(self.keepListButton)
} }
// 拿到当前类型垃圾桶数据的最后一个 // 拿到当前类型垃圾桶数据的最后一个
...@@ -277,15 +356,13 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -277,15 +356,13 @@ class PhotoRemoveViewController: BaseViewController {
self.navView.resetButton.isHidden = true self.navView.resetButton.isHidden = true
} }
} }
}
if assetModel.count > 0 {
if assetModel.count > 0 { // 显示垃圾桶
// 显示垃圾桶 self.trashSubView.resourceCountlabel.text = String(assetModel.count)
self.trashSubView.resourceCountlabel.text = String(assetModel.count) self.showTrashView()
self.showTrashView() }else{
}else{ self.hideTrashView()
self.hideTrashView()
}
} }
} }
} }
...@@ -297,7 +374,7 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -297,7 +374,7 @@ class PhotoRemoveViewController: BaseViewController {
let photoView = PhotosRemoveBaseView() let photoView = PhotosRemoveBaseView()
photoView.mediaType = self.mediaType photoView.mediaType = self.mediaType
photoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan))) photoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan)))
view.addSubview(photoView) self.view.addSubview(photoView)
photoView.frame = CGRectMake(0, 0, self.view.width - 30, 509 * RScreenH()) photoView.frame = CGRectMake(0, 0, self.view.width - 30, 509 * RScreenH())
photoView.center = self.view.center photoView.center = self.view.center
photoViews.append(photoView) photoViews.append(photoView)
...@@ -435,7 +512,9 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -435,7 +512,9 @@ class PhotoRemoveViewController: BaseViewController {
} }
} }
}else { }else {
view.configure(with: PhotoAndVideoMananger.mananger.getImageFromAssetID(id: dataModel[imageIndex].localIdentifier) ?? UIImage()) PhotoAndVideoMananger.mananger.asynGetImageFromAssetID(id: dataModel[imageIndex].localIdentifier) { image in
view.configure(with: image)
}
} }
} }
...@@ -446,10 +525,16 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -446,10 +525,16 @@ class PhotoRemoveViewController: BaseViewController {
private func updateActionButton(for view: PhotosRemoveBaseView, translation: CGPoint) { private func updateActionButton(for view: PhotosRemoveBaseView, translation: CGPoint) {
if translation.x > 0 { if translation.x > 0 {
self.trashButton.isSelected = false
self.keepListButton.isSelected = true
view.showRightButton() view.showRightButton()
} else if translation.x < 0 { } else if translation.x < 0 {
self.trashButton.isSelected = true
self.keepListButton.isSelected = false
view.showLeftButton() view.showLeftButton()
} else { } else {
self.keepListButton.isSelected = false
self.trashButton.isSelected = false
view.hideButtons() view.hideButtons()
} }
} }
...@@ -478,6 +563,7 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -478,6 +563,7 @@ class PhotoRemoveViewController: BaseViewController {
view.center = self.view.center view.center = self.view.center
view.hideButtons() view.hideButtons()
} }
initTrashAndKeepListButton()
} }
private func recycleView() { private func recycleView() {
...@@ -505,16 +591,23 @@ class PhotoRemoveViewController: BaseViewController { ...@@ -505,16 +591,23 @@ class PhotoRemoveViewController: BaseViewController {
// 视图层级处理 // 视图层级处理
bringCurrentViewToFront() bringCurrentViewToFront()
newView.alpha = 1
UIView.animate(withDuration: 0.3) { initTrashAndKeepListButton()
newView.alpha = 1 }
}
func initTrashAndKeepListButton(){
self.trashButton.isSelected = false
self.keepListButton.isSelected = false
self.view.bringSubviewToFront(self.trashButton)
self.view.bringSubviewToFront(self.keepListButton)
} }
private func bringCurrentViewToFront() { private func bringCurrentViewToFront() {
guard photoViews.count >= 2 else { return } guard photoViews.count >= 2 else { return }
view.bringSubviewToFront(photoViews[1]) view.bringSubviewToFront(photoViews[1])
view.bringSubviewToFront(photoViews[0]) view.bringSubviewToFront(photoViews[0])
view.bringSubviewToFront(self.trashButton)
view.bringSubviewToFront(self.keepListButton)
} }
......
...@@ -60,10 +60,10 @@ class HomeCollectionViewHeader : UICollectionReusableView { ...@@ -60,10 +60,10 @@ class HomeCollectionViewHeader : UICollectionReusableView {
self.addSubview(self.permissionView) self.addSubview(self.permissionView)
self.permissionView.snp.makeConstraints { make in self.permissionView.snp.makeConstraints { make in
make.top.equalTo(self.progressBar.snp.bottom).offset(-92) make.top.equalTo(progressBar.snp.bottom).offset(10)
make.left.equalToSuperview().offset(-22) make.left.equalToSuperview().offset(-22)
make.right.equalToSuperview().offset(22) make.right.equalToSuperview().offset(22)
make.height.equalTo(449) make.height.equalTo(340)
} }
...@@ -206,6 +206,7 @@ class CustomProgressBar: UIView { ...@@ -206,6 +206,7 @@ class CustomProgressBar: UIView {
progressLayer.frame = CGRect(x: 0, y: 0, width: ScreenW-48, height: 10) progressLayer.frame = CGRect(x: 0, y: 0, width: ScreenW-48, height: 10)
progressLayer.cornerRadius = 5 progressLayer.cornerRadius = 5
progressLayer.masksToBounds = true progressLayer.masksToBounds = true
updateProgress()
} }
......
...@@ -135,11 +135,4 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView { ...@@ -135,11 +135,4 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
GETCURRENTNAV()?.pushViewController(vc, animated: true) 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)
}
} }
...@@ -51,7 +51,7 @@ class HomeView:UIView { ...@@ -51,7 +51,7 @@ class HomeView:UIView {
sview.register(HomeTitleCollectionCell.self, forCellWithReuseIdentifier: HomeTitleCollectionCell.identifiers) sview.register(HomeTitleCollectionCell.self, forCellWithReuseIdentifier: HomeTitleCollectionCell.identifiers)
sview.register(HomeOtherCollectionCell.self, forCellWithReuseIdentifier: HomeOtherCollectionCell.identifier) sview.register(HomeOtherCollectionCell.self, forCellWithReuseIdentifier: HomeOtherCollectionCell.identifier)
sview.register(HomeVideoCoverCell.classForCoder(), forCellWithReuseIdentifier: "HomeVideoCoverCell")
sview.register(HomeCollectionViewHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HomeCollectionViewHeader") sview.register(HomeCollectionViewHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HomeCollectionViewHeader")
sview.register(UICollectionReusableView.self,forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,withReuseIdentifier: "HomeCollectionViewFooter") sview.register(UICollectionReusableView.self,forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,withReuseIdentifier: "HomeCollectionViewFooter")
...@@ -278,11 +278,19 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -278,11 +278,19 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
return cell return cell
case 1: case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell if viewModel.cardGroup[indexPath.row].folderName == HomeUIEnum.SimilarVideos.title || viewModel.cardGroup[indexPath.row].folderName == HomeUIEnum.Videos.title{
cell.dealMediaType(indexPath.row) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeVideoCoverCell", for: indexPath) as! HomeVideoCoverCell
let model = viewModel.cardGroup[indexPath.row] let model = viewModel.cardGroup[indexPath.row]
cell.reloadUIWithModel(model: model) cell.reloadUIWithModel(model: model)
return cell return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell
cell.dealMediaType(indexPath.row)
let model = viewModel.cardGroup[indexPath.row]
cell.reloadUIWithModel(model: model)
return cell
}
default: default:
return UICollectionViewCell() return UICollectionViewCell()
} }
...@@ -402,9 +410,9 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -402,9 +410,9 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize { func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize {
if section == 0 { if section == 0 {
if PhotoManager.shared.permissionStatus == .authorized{ if PhotoManager.shared.permissionStatus == .authorized{
return CGSize(width: self.collectionView.width, height: 76 + (self.homeNavView?.height ?? 0)) return CGSize(width:ScreenW-(marginLR*2), height: 76 + (self.homeNavView?.height ?? 0))
}else{ }else{
return CGSize(width: self.collectionView.width, height: 404) return CGSize(width:ScreenW-(marginLR*2), height: 450)
} }
} }
return CGSize.zero return CGSize.zero
......
...@@ -71,7 +71,8 @@ class PhotosRemoveBaseView: UIView { ...@@ -71,7 +71,8 @@ class PhotosRemoveBaseView: UIView {
imageView.contentMode = .scaleAspectFit imageView.contentMode = .scaleAspectFit
addSubview(imageView) addSubview(imageView)
leftButton.frame = CGRect(x: self.width / 2 - 45.5, y: self.height / 2 - 21, width: 95, height: 42) leftButton.frame = CGRect(x: 0, y: 0, width: 95, height: 42)
leftButton.center = imageView.center
leftButton.setTitle("Delete", for: .normal) leftButton.setTitle("Delete", for: .normal)
leftButton.backgroundColor = UIColor(red: 0.95, green: 0.21, blue: 0.21, alpha: 1) leftButton.backgroundColor = UIColor(red: 0.95, green: 0.21, blue: 0.21, alpha: 1)
leftButton.tintColor = .white leftButton.tintColor = .white
...@@ -81,7 +82,8 @@ class PhotosRemoveBaseView: UIView { ...@@ -81,7 +82,8 @@ class PhotosRemoveBaseView: UIView {
addSubview(leftButton) addSubview(leftButton)
rightButton.frame = CGRect(x: self.width / 2 - 45.5, y: self.height / 2 - 21, width: 95, height: 42) rightButton.frame = CGRect(x: 0, y: 0, width: 95, height: 42)
rightButton.center = imageView.center
rightButton.setTitle("Retain", for: .normal) rightButton.setTitle("Retain", for: .normal)
rightButton.backgroundColor = UIColor(red: 0.18, green: 0.76, blue: 0.35, alpha: 1) rightButton.backgroundColor = UIColor(red: 0.18, green: 0.76, blue: 0.35, alpha: 1)
rightButton.tintColor = .white rightButton.tintColor = .white
...@@ -99,7 +101,7 @@ class PhotosRemoveBaseView: UIView { ...@@ -99,7 +101,7 @@ class PhotosRemoveBaseView: UIView {
} }
func showLeftButton() { func showLeftButton() {
self.leftButton.center = self.center self.leftButton.center = imageView.center
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) {
self.leftButton.alpha = 1 self.leftButton.alpha = 1
self.maskTempleteView.backgroundColor = UIColor(red: 0.95, green: 0.21, blue: 0.21, alpha: 0.4000) self.maskTempleteView.backgroundColor = UIColor(red: 0.95, green: 0.21, blue: 0.21, alpha: 0.4000)
...@@ -109,7 +111,7 @@ class PhotosRemoveBaseView: UIView { ...@@ -109,7 +111,7 @@ class PhotosRemoveBaseView: UIView {
} }
func showRightButton() { func showRightButton() {
self.rightButton.center = self.center self.rightButton.center = imageView.center
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) {
self.rightButton.alpha = 1 self.rightButton.alpha = 1
self.maskTempleteView.backgroundColor = UIColor(red: 0.26, green: 0.78, blue: 0.41, alpha: 0.4000) self.maskTempleteView.backgroundColor = UIColor(red: 0.26, green: 0.78, blue: 0.41, alpha: 0.4000)
...@@ -119,12 +121,12 @@ class PhotosRemoveBaseView: UIView { ...@@ -119,12 +121,12 @@ class PhotosRemoveBaseView: UIView {
} }
func hideButtons() { func hideButtons() {
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) { [self] in
self.leftButton.alpha = 0 self.leftButton.alpha = 0
self.rightButton.alpha = 0 self.rightButton.alpha = 0
self.maskTempleteView.removeFromSuperview() self.maskTempleteView.removeFromSuperview()
self.leftButton.center = self.center self.leftButton.center = self.imageView.center
self.rightButton.center = self.center self.rightButton.center = self.imageView.center
} }
} }
......
...@@ -12,7 +12,7 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -12,7 +12,7 @@ class HomeTitleCollectionCell:UICollectionViewCell {
static let identifiers = "HomeTitleCollectionCellID" static let identifiers = "HomeTitleCollectionCellID"
private var titleLabel: UILabel? private var titleLabel: UILabel!
public var fileLabel:UILabel? public var fileLabel:UILabel?
...@@ -48,8 +48,8 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -48,8 +48,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
titleLabel = UILabel() titleLabel = UILabel()
titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold) titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .bold)
titleLabel?.textColor = UIColor.colorWithHex(hexStr: black3Color) titleLabel.textColor = UIColor.colorWithHex(hexStr: black3Color)
fileLabel = UILabel() fileLabel = UILabel()
fileLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold) fileLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
...@@ -118,8 +118,8 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -118,8 +118,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
guard let model = model else { return } guard let model = model else { return }
self.model = model self.model = model
titleLabel?.text = model.folderName titleLabel.text = model.folderName
titleLabel?.sizeToFit() titleLabel.sizeToFit()
var count = 0 var count = 0
...@@ -137,8 +137,8 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -137,8 +137,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
assetsModels = assets.compactMap({ model in assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model) return ImageCollectionModel.init(asset: model)
}) })
collectionView?.reloadData()
} }
collectionView?.reloadData()
} }
...@@ -155,12 +155,12 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -155,12 +155,12 @@ class HomeTitleCollectionCell:UICollectionViewCell {
super.layoutSubviews() super.layoutSubviews()
titleLabel?.snp.makeConstraints({ make in titleLabel.snp.makeConstraints({ make in
make.top.left.equalTo(16) make.top.left.equalTo(16)
}) })
titleLabel?.sizeToFit() titleLabel.sizeToFit()
fileLabel?.snp.makeConstraints({ make in fileLabel?.snp.makeConstraints({ make in
...@@ -172,9 +172,9 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -172,9 +172,9 @@ class HomeTitleCollectionCell:UICollectionViewCell {
collectionView?.snp.makeConstraints({ make in collectionView?.snp.makeConstraints({ make in
make.left.equalTo(16) make.left.equalTo(16)
make.bottom.equalTo(-16) make.bottom.equalTo(0)
make.right.equalTo(-16) make.right.equalTo(-16)
make.top.equalTo(48) make.top.equalTo(titleLabel.snp.bottom).offset(13)
}) })
nextImage?.snp.makeConstraints({ make in nextImage?.snp.makeConstraints({ make in
...@@ -205,7 +205,7 @@ extension HomeTitleCollectionCell:UICollectionViewDelegate,UICollectionViewDataS ...@@ -205,7 +205,7 @@ extension HomeTitleCollectionCell:UICollectionViewDelegate,UICollectionViewDataS
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 计算 cell 宽度 // 计算 cell 宽度
return CGSize(width:collectionView.height - 2.5, height: collectionView.height - 2.5) // 宽高相等,形成网格 return CGSize(width:collectionView.height - 18, height: collectionView.height - 18) // 宽高相等,形成网格
} }
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
......
//
// HomeVideoCoverCell.swift
// PhoneManager
//
// Created by edy on 2025/5/20.
//
import UIKit
import AVKit
class HomeVideoCoverCell: UICollectionViewCell {
var videoUrl:URL?
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .white
self.contentView.addSubview(self.titleLabel)
self.contentView.addSubview(self.imageView)
contentView.layer.addSublayer(playerLayer)
self.contentView.addSubview(self.infoBackView)
self.infoBackView.addSubview(self.countLabel)
self.infoBackView.addSubview(self.sizeLabel)
self.infoBackView.addSubview(self.moreImageView)
}
func reloadUIWithModel(model:HomePhotosModel?){
// 1. 先重置状态
guard let model else {
playerLayer.isHidden = true
videoPlayer.pause()
return
}
// 设置标题
self.titleLabel.text = model.folderName
// 获取数量
var count = 0
for item in model.assets {
count = count + item.count
}
// 设置数量文字
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))
}
// 2. 设置新数据
if let id = model.assets.first?.first?.localIdentifier{
// 保存当前的 identifier 用于验证
let currentID = id
imageView.asset.load(withLocalIdentifier:id,placeholder: UIImage(named: "videosmoren"))
PhotoManager.shared.getVideoURL(localIdentifier:id, completion: {[weak self] url in
guard let weakSelf = self else { return }
// 验证 ID 是否匹配,避免 cell 重用导致的错误
if currentID == id {
weakSelf.configure(with: url)
}
})
}else{
self.imageView.image = UIImage.init(named: "videosmoren")
playerLayer.isHidden = true
videoPlayer.pause()
}
}
public let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.layer.cornerRadius = 8
iv.backgroundColor = .clear
return iv
}()
private let infoBackView: UIView = {
let iv = UIView()
iv.clipsToBounds = true
iv.layer.cornerRadius = 8
iv.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4)
return iv
}()
public var countLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textAlignment = .left
return label
}()
let sizeLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textAlignment = .left
return label
}()
private let moreImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "icon_left_setting_grey")
iv.backgroundColor = .clear
return iv
}()
private let titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor.colorWithHex(hexStr: black3Color)
label.textAlignment = .left
label.numberOfLines = 0
return label
}()
override func layoutSubviews() {
self.layer.cornerRadius = 12
self.layer.masksToBounds = true
imageView.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(8)
make.left.equalToSuperview().offset(16)
make.width.equalToSuperview().offset(-32)
make.height.equalTo(self.width - 32)
}
self.infoBackView.snp.makeConstraints { make in
make.left.right.bottom.equalTo(imageView)
make.height.equalTo(40)
}
titleLabel.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(16)
make.width.equalToSuperview().offset(-32)
}
self.countLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.top.equalToSuperview().offset(3)
make.height.equalTo(20)
}
self.sizeLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.top.equalTo(countLabel.snp.bottom).offset(-2)
make.height.equalTo(14)
}
self.moreImageView.snp.makeConstraints { make in
make.width.height.equalTo(20)
make.right.equalToSuperview().offset(-12)
make.centerY.equalToSuperview()
}
playerLayer.frame = imageView.frame
playerLayer.cornerRadius = 8
playerLayer.masksToBounds = true
}
lazy var videoPlayer:AVPlayer = {
let palyer = AVPlayer.init()
palyer.volume = 0
// 设置音频会话,允许混音
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
try? AVAudioSession.sharedInstance().setActive(true)
return palyer
}()
lazy var playerLayer:AVPlayerLayer = {
let playerLayer = AVPlayerLayer.init(player: videoPlayer)
playerLayer.backgroundColor = UIColor.black.cgColor
playerLayer.isHidden = true
return playerLayer
}()
deinit{
NotificationCenter.default.removeObserver(self)
}
}
extension HomeVideoCoverCell{
func configure(with videoURL: URL?) {
guard let videoURL = videoURL else{
playerLayer.isHidden = true
videoPlayer.pause()
return
}
playerLayer.isHidden = false
if videoURL == videoUrl{
Print("地址相同,无需刷新")
return
}
videoUrl = videoURL
Print("地址不同,需刷新")
let item = AVPlayerItem.init(url: videoURL)
videoPlayer.replaceCurrentItem(with: item)
videoPlayer.play()
NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishPlaying),
name: .AVPlayerItemDidPlayToEndTime,
object: item)
}
@objc private func playerDidFinishPlaying() {
videoPlayer.seek(to: .zero)
videoPlayer.play()
}
}
...@@ -121,6 +121,10 @@ class HomeViewModel { ...@@ -121,6 +121,10 @@ class HomeViewModel {
return return
} }
guard photoManager.permissionStatus == .authorized else{
return
}
totalFilesCount = photoManager.allAssets.count totalFilesCount = photoManager.allAssets.count
hadLoad = true hadLoad = true
......
...@@ -83,6 +83,7 @@ class MaintaiDetailViewController: BaseViewController { ...@@ -83,6 +83,7 @@ class MaintaiDetailViewController: BaseViewController {
if isSelectAll{ if isSelectAll{
let ids = viewModel.souces.flatMap{$0}.compactMap{$0.localIdentifier} let ids = viewModel.souces.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){ if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
selectAsset.removeAll()
self.navigationController?.popViewController(animated: true) self.navigationController?.popViewController(animated: true)
} }
isSelectAll = false isSelectAll = false
...@@ -178,7 +179,7 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{ ...@@ -178,7 +179,7 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
} }
// 拿到当前在选中数据的下标 // 拿到当前在选中数据的下标
let selectIndex = selectAsset.firstIndex(where: { var selectIndex = selectAsset.firstIndex(where: {
$0 == cell.selectAsset $0 == cell.selectAsset
}) })
...@@ -194,11 +195,15 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{ ...@@ -194,11 +195,15 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
weakSelf.selectAsset[sIndex] = selects weakSelf.selectAsset[sIndex] = selects
}else{ }else{
weakSelf.selectAsset.remove(at: sIndex) weakSelf.selectAsset.remove(at: sIndex)
selectIndex = nil
} }
}else{ }else{
Print("没有任何选中数据") Print("没有任何选中数据")
weakSelf.selectAsset.append(selects) weakSelf.selectAsset.append(selects)
selectIndex = weakSelf.selectAsset.firstIndex(where: {
$0 == selects
})
} }
weakSelf.dealMutilImagePick() weakSelf.dealMutilImagePick()
} }
...@@ -241,8 +246,10 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{ ...@@ -241,8 +246,10 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
}else{ }else{
maintaiBottomView.disMiss() maintaiBottomView.disMiss()
} }
// tableView.reloadData()
tableView.reloadData() // UIView.performWithoutAnimation {
// self.tableView.reloadData()
// }
} }
func dealPickAssetModel(index:Int){ func dealPickAssetModel(index:Int){
......
...@@ -21,10 +21,10 @@ ...@@ -21,10 +21,10 @@
<rect key="frame" x="0.0" y="0.0" width="269" height="253"/> <rect key="frame" x="0.0" y="0.0" width="269" height="253"/>
</imageView> </imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oJ5-js-pjw"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oJ5-js-pjw">
<rect key="frame" x="234" y="213" width="30" height="30"/> <rect key="frame" x="235" y="219" width="34" height="34"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="30" id="gGs-ha-T24"/> <constraint firstAttribute="width" constant="34" id="gGs-ha-T24"/>
<constraint firstAttribute="height" constant="30" id="mvi-bY-CFY"/> <constraint firstAttribute="height" constant="34" id="mvi-bY-CFY"/>
</constraints> </constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/> <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="icon_maintai_unselect_big"/> <state key="normal" image="icon_maintai_unselect_big"/>
...@@ -38,8 +38,8 @@ ...@@ -38,8 +38,8 @@
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/> <viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="T4a-zL-UKQ" secondAttribute="bottom" id="B7R-M8-dpU"/> <constraint firstAttribute="bottom" secondItem="T4a-zL-UKQ" secondAttribute="bottom" id="B7R-M8-dpU"/>
<constraint firstAttribute="bottom" secondItem="oJ5-js-pjw" secondAttribute="bottom" constant="10" id="BfF-Td-Tcc"/> <constraint firstAttribute="bottom" secondItem="oJ5-js-pjw" secondAttribute="bottom" id="BfF-Td-Tcc"/>
<constraint firstAttribute="trailing" secondItem="oJ5-js-pjw" secondAttribute="trailing" constant="5" id="Iw9-UA-zNe"/> <constraint firstAttribute="trailing" secondItem="oJ5-js-pjw" secondAttribute="trailing" id="Iw9-UA-zNe"/>
<constraint firstItem="T4a-zL-UKQ" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="MoV-lR-Hqz"/> <constraint firstItem="T4a-zL-UKQ" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="MoV-lR-Hqz"/>
<constraint firstItem="T4a-zL-UKQ" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="dJs-Pa-MhO"/> <constraint firstItem="T4a-zL-UKQ" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="dJs-Pa-MhO"/>
<constraint firstAttribute="trailing" secondItem="T4a-zL-UKQ" secondAttribute="trailing" id="gyj-E3-guF"/> <constraint firstAttribute="trailing" secondItem="T4a-zL-UKQ" secondAttribute="trailing" id="gyj-E3-guF"/>
......
...@@ -43,7 +43,7 @@ class MaintaiDetailImageSmallCell: UICollectionViewCell { ...@@ -43,7 +43,7 @@ class MaintaiDetailImageSmallCell: UICollectionViewCell {
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside) selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0) make.right.bottom.equalTo(0)
make.size.equalTo(20) make.size.equalTo(30)
} }
} }
......
...@@ -58,8 +58,8 @@ class MaintaiDetailTableViewCell: UITableViewCell { ...@@ -58,8 +58,8 @@ class MaintaiDetailTableViewCell: UITableViewCell {
func configChild(){ func configChild(){
let layout = UICollectionViewFlowLayout() let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: smallW, height: smallW) layout.itemSize = CGSize(width: smallW, height: smallW)
layout.minimumLineSpacing = 8 layout.minimumLineSpacing = 12
layout.minimumInteritemSpacing = 8 layout.minimumInteritemSpacing = 12
layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12) layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
layout.scrollDirection = .horizontal layout.scrollDirection = .horizontal
...@@ -135,8 +135,9 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{ ...@@ -135,8 +135,9 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
Print("主图滑动距离",scrollView.contentOffset.x) Print("主图滑动距离",scrollView.contentOffset.x)
let index = Int(scrollView.contentOffset.x/bigW) let index = Int(scrollView.contentOffset.x/bigW)
Print("小图滑动到下标\(index)") Print("小图滑动到下标\(index)")
let space:CGFloat = (self.smallW + 12) * CGFloat(index)
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
self.pageCollectionView.contentOffset = CGPoint(x: index * Int(self.smallW+8)+4, y: 0) self.pageCollectionView.contentOffset = CGPoint(x:space, y: 0)
} }
} }
} }
...@@ -183,8 +184,9 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{ ...@@ -183,8 +184,9 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
Print("小图滑动距离",offsetX) Print("小图滑动距离",offsetX)
let index = Int((offsetX/(smallW+12)).rounded()) //Int(offsetX/(smallW+12).rounded()) //rounded(offsetX/smallW) let index = Int((offsetX/(smallW+12)).rounded()) //Int(offsetX/(smallW+12).rounded()) //rounded(offsetX/smallW)
Print("大图滑动到下标\(index)") Print("大图滑动到下标\(index)")
let space:CGFloat = (self.smallW + 12) * CGFloat(index)
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
self.pageCollectionView.contentOffset = CGPoint(x: index * Int(self.smallW+8)+4, y: 0) self.pageCollectionView.contentOffset = CGPoint(x: space, y: 0)
} }
self.mainCollectionView.contentOffset = CGPoint(x: index * Int(self.bigW), y: 0) self.mainCollectionView.contentOffset = CGPoint(x: index * Int(self.bigW), y: 0)
draggView = .null draggView = .null
......
...@@ -37,23 +37,20 @@ class MaintaiDetialVideoCell: UITableViewCell { ...@@ -37,23 +37,20 @@ class MaintaiDetialVideoCell: UITableViewCell {
contentView.bringSubviewToFront(selectBtn) contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside) selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in selectBtn.snp.makeConstraints { make in
make.right.equalTo(-15) make.right.equalTo(-22)
make.bottom.equalTo(-10) make.bottom.equalTo(0)
make.size.equalTo(30) make.size.equalTo(34)
} }
} }
private func setupPlayer() { private func setupPlayer() {
// 创建播放器层
// playerLayer = AVPlayerLayer()
// playerLayer?.videoGravity = .resizeAspect
contentView.layer.addSublayer(playerLayer) contentView.layer.addSublayer(playerLayer)
} }
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
// 设置播放器层的frame // 设置播放器层的frame
playerLayer.frame = CGRect(x: 15, y: 10, width: contentView.width-30, height: contentView.height-20) playerLayer.frame = CGRect(x: 22, y: 10, width: contentView.width-44, height: contentView.height-10)
playerLayer.cornerRadius = 15 playerLayer.cornerRadius = 15
playerLayer.masksToBounds = true playerLayer.masksToBounds = true
} }
...@@ -93,53 +90,34 @@ class MaintaiDetialVideoCell: UITableViewCell { ...@@ -93,53 +90,34 @@ class MaintaiDetialVideoCell: UITableViewCell {
return return
} }
if videoPlayer.rate != 0{
return
}
let item = AVPlayerItem.init(url: videoURL) let item = AVPlayerItem.init(url: videoURL)
videoPlayer.replaceCurrentItem(with: item) videoPlayer.replaceCurrentItem(with: item)
videoPlayer.play() videoPlayer.play()
// // 创建播放器项
// let playerItem = AVPlayerItem(url: videoURL)
//
// // 创建播放器
// player = AVPlayer(playerItem: playerItem)
// playerLayer?.player = player
//
// // 设置静音
// player?.volume = 0
//
// // 播放视频
// player?.play()
//
// 添加播放完成的观察者
NotificationCenter.default.addObserver(self, NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishPlaying), selector: #selector(playerDidFinishPlaying),
name: .AVPlayerItemDidPlayToEndTime, name: .AVPlayerItemDidPlayToEndTime,
object: item) object: item)
} }
//
@objc private func playerDidFinishPlaying() { @objc private func playerDidFinishPlaying() {
// // 播放结束后不做任何操作,因为只需要播放一次
// player?.pause()
// player?.seek(to: .zero)
//playerLayer.player?.seek(to: .zero)
videoPlayer.seek(to: .zero) videoPlayer.seek(to: .zero)
videoPlayer.play() videoPlayer.play()
} }
//
// override func prepareForReuse() {
// super.prepareForReuse()
// // 清理播放器
// player?.pause()
// player = nil
// NotificationCenter.default.removeObserver(self)
// }
//
@objc func selectChange(){ @objc func selectChange(){
selectChangeBlock?() selectChangeBlock?()
} }
deinit{
NotificationCenter.default.removeObserver(self)
}
} }
...@@ -24,6 +24,7 @@ class NewGuideViewController: UIViewController { ...@@ -24,6 +24,7 @@ class NewGuideViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.transitioningDelegate = self
configUI() configUI()
startTimer() startTimer()
} }
...@@ -81,24 +82,27 @@ class NewGuideViewController: UIViewController { ...@@ -81,24 +82,27 @@ class NewGuideViewController: UIViewController {
} }
func enterHome(){ func enterHome(){
let vc:HomeViewController = HomeViewController() // let vc:HomeViewController = HomeViewController()
//
let nav = BaseNavViewController(rootViewController: vc) // let nav = BaseNavViewController(rootViewController: vc)
//
// cWindow?.rootViewController = nav
//
// let transition = CATransition()
// transition.duration = 0.5
// transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
//
// // 添加动画到 window 的 layer
// cWindow?.layer.add(transition, forKey: kCATransition)
//
// // 显示 window
// cWindow?.makeKeyAndVisible()
cWindow?.rootViewController = nav
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
// 添加动画到 window 的 layer
cWindow?.layer.add(transition, forKey: kCATransition)
// 显示 window
cWindow?.makeKeyAndVisible()
self.navigationController?.popToRootViewController(animated: false)
UserDef.shard.isShowLanding = true UserDef.shard.isShowLanding = true
UserDef.shard.saveUserDefToSandBox() UserDef.shard.saveUserDefToSandBox()
NotificationCenter.default.post(name: .guidePageClose, object: nil)
} }
deinit{ deinit{
...@@ -145,3 +149,10 @@ extension NewGuideViewController:UICollectionViewDelegate,UICollectionViewDataSo ...@@ -145,3 +149,10 @@ extension NewGuideViewController:UICollectionViewDelegate,UICollectionViewDataSo
} }
} }
extension NewGuideViewController: UIViewControllerTransitioningDelegate {
func animationController(forDismissed dismissedController: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// pop 时触发消失动画(等同于 popToRootViewController)
return FadeOutPopTransition()
}
}
//
// HomeLaunchView.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
import Lottie
class HomeLaunchView:UIView {
var disMissBlock:(() ->Void)?
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
configUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configUI(){
backgroundColor = UIColor.colorWithHex(hexStr: "#0082FF")
let logoImage = UIImageView()
logoImage.image = UIImage.init(named: "icon_phone_manager")
addSubview(logoImage)
let nameImage = UIImageView()
nameImage.image = UIImage.init(named: "icon_phone_manager_name")
addSubview(nameImage)
addSubview(LaunchingLoop)
logoImage.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(kSafeAreaInsets.top+150)
}
nameImage.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(logoImage.snp.bottom).offset(12)
}
LaunchingLoop.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.height.equalTo(150)
make.bottom.equalToSuperview().offset(-60 * RScreenH())
}
}
func show(){
KEYWINDOW()?.addSubview(self)
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
self.alpha = 1
UIView.animate(withDuration: 0.3) {
self.alpha = 0
}completion: { _ in
self.removeFromSuperview()
}
self.disMissBlock?()
})
}
private lazy var LaunchingLoop: LottieAnimationView = {
let animationView = LottieAnimationView(name: "launch_loaing")
animationView.loopMode = .loop
animationView.play()
return animationView
}()
}
...@@ -29,40 +29,33 @@ class LauchVC:UIViewController { ...@@ -29,40 +29,33 @@ class LauchVC:UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// backView.addSubview(LaunchingView) view.addSubview(LaunchingLoop)
backView.addSubview(LaunchingLoop)
// LaunchingView.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.centerY.equalToSuperview().offset(-140 * RScreenH())
// make.width.equalToSuperview()
// make.height.equalTo(LaunchingView.snp.width)
// }
LaunchingLoop.snp.makeConstraints { make in LaunchingLoop.snp.makeConstraints { make in
make.centerX.equalToSuperview() make.centerX.equalToSuperview()
make.width.height.equalTo(150) make.width.height.equalTo(150)
make.bottom.equalToSuperview().offset(-60 * RScreenH()) make.bottom.equalToSuperview().offset(-60 * RScreenH())
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: { DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
var vc:UIViewController? self.dismiss(animated: false)
if (UserDef.shard.isShowLanding) { // var vc:UIViewController?
vc = HomeViewController() // if (UserDef.shard.isShowLanding) {
}else { // vc = HomeViewController()
let Ssoryboard = UIStoryboard(name: "PermissionVC", bundle: nil) // }else {
if let current = Ssoryboard.instantiateViewController(identifier: "PermissionVCID") as? PermissionVC { // let Ssoryboard = UIStoryboard(name: "PermissionVC", bundle: nil)
vc = current // if let current = Ssoryboard.instantiateViewController(identifier: "PermissionVCID") as? PermissionVC {
} // vc = current
} // }
guard let vc else {return} // }
let nav = BaseNavViewController(rootViewController: vc) // guard let vc else {return}
cWindow?.rootViewController = nav // let nav = BaseNavViewController(rootViewController: vc)
let transition = CATransition() // cWindow?.rootViewController = nav
transition.duration = 0.5 // let transition = CATransition()
transition.subtype = CATransitionSubtype.fromRight // 从左侧推入 // transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) // transition.subtype = CATransitionSubtype.fromRight // 从左侧推入
cWindow?.layer.add(transition, forKey: kCATransition) // transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
cWindow?.makeKeyAndVisible() // cWindow?.layer.add(transition, forKey: kCATransition)
// cWindow?.makeKeyAndVisible()
}) })
} }
} }
//
// LaunchViewController.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
import Lottie
class LaunchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.colorWithHex(hexStr: "#0082FF")
let logoImage = UIImageView()
logoImage.image = UIImage.init(named: "icon_phone_manager")
view.addSubview(logoImage)
let nameImage = UIImageView()
nameImage.image = UIImage.init(named: "icon_phone_manager_name")
view.addSubview(nameImage)
view.addSubview(LaunchingLoop)
logoImage.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(kSafeAreaInsets.top+150)
}
nameImage.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(logoImage.snp.bottom).offset(12)
}
LaunchingLoop.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.height.equalTo(150)
make.bottom.equalToSuperview().offset(-60 * RScreenH())
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
self.dismiss(animated: true)
})
}
private lazy var LaunchingLoop: LottieAnimationView = {
let animationView = LottieAnimationView(name: "launch_loaing")
animationView.loopMode = .loop
animationView.play()
return animationView
}()
}
...@@ -46,8 +46,11 @@ class HomePayDueView: UIView { ...@@ -46,8 +46,11 @@ class HomePayDueView: UIView {
func reloadUI(_ type:Int,week:SKProduct?,life:SKProduct?){ func reloadUI(_ type:Int,week:SKProduct?,life:SKProduct?){
guard let pord = week,let pord1 = life else { return } guard let pord = week,let pord1 = life else {
self.isHidden = true
return
}
self.isHidden = false
if type == 0{ if type == 0{
// 免费试用订阅 // 免费试用订阅
dueDay.text = "Due today" dueDay.text = "Due today"
......
...@@ -102,10 +102,6 @@ class HomePayView:UIView { ...@@ -102,10 +102,6 @@ class HomePayView:UIView {
let animationView = LottieAnimationView(name: "iapAnimation") let animationView = LottieAnimationView(name: "iapAnimation")
animationView.loopMode = .loop animationView.loopMode = .loop
// animationView.animationSpeed = -1.0
// animationView.layer.cornerRadius = 8
// animationView.contentMode = .scaleAspectFill
// animationView.clipsToBounds = true
return animationView return animationView
}() }()
...@@ -115,6 +111,7 @@ class HomePayView:UIView { ...@@ -115,6 +111,7 @@ class HomePayView:UIView {
super.init(frame: frame) super.init(frame: frame)
setupUI() setupUI()
setPayAnime()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
...@@ -161,64 +158,7 @@ class HomePayView:UIView { ...@@ -161,64 +158,7 @@ class HomePayView:UIView {
} }
titleLabel2?.sizeToFit() titleLabel2?.sizeToFit()
// photoImage = UIImageView(image: UIImage(named: "img_photos_start"))
// self.addSubview(photoImage)
//
// photoImage.snp.makeConstraints { make in
// make.top.equalTo(titleLabel2!.snp.bottom).offset(38.RH())
// make.left.equalTo(48.RW())
// make.size.equalTo(64.RW())
// }
//
// icloudImage = UIImageView(image: UIImage(named: "img_file_guide"))
// self.addSubview(icloudImage)
//
// icloudImage.snp.makeConstraints { make in
// make.left.equalTo(photoImage.snp.right).offset(24.RW())
// make.centerY.equalTo(photoImage)
// make.size.equalTo(64.RW())
// }
//
// photoLabel = UILabel()
// photoLabel?.text = "403"
// photoLabel?.backgroundColor = UIColor.colorWithHex(hexStr: "#EB4545")
// photoLabel?.font = UIFont.scaledSystemFont(ofSize: 10, weight: .bold)
// photoLabel?.textAlignment = .center
// photoLabel?.textColor = .white
// photoLabel?.layer.cornerRadius = 12.RW()
// photoLabel?.layer.masksToBounds = true
// self.addSubview(photoLabel!)
//
// photoLabel?.snp.makeConstraints { make in
// make.top.equalTo(photoImage.snp.top).offset(-12)
// make.right.equalTo(photoImage.snp.right).offset(12)
// make.size.equalTo(24.RW())
// }
//
// photoLabel?.sizeToFit()
//
// icloudLabel = UILabel()
// icloudLabel?.text = "217"
// icloudLabel?.textAlignment = .center
// icloudLabel?.font = UIFont.scaledSystemFont(ofSize: 10, weight: .bold)
// icloudLabel?.backgroundColor = UIColor.colorWithHex(hexStr: "#EB4545")
// icloudLabel?.textColor = .white
// icloudLabel?.layer.cornerRadius = 12.RW()
// icloudLabel?.layer.masksToBounds = true
// self.addSubview(icloudLabel!)
//
// icloudLabel?.snp.makeConstraints { make in
// make.top.equalTo(icloudImage.snp.top).offset(-12)
// make.right.equalTo(icloudImage.snp.right).offset(12)
// make.size.equalTo(24.RW())
// }
//
// icloudLabel?.sizeToFit()
//
self.addSubview(animationView) self.addSubview(animationView)
playAnimationWithDelay() playAnimationWithDelay()
...@@ -228,27 +168,6 @@ class HomePayView:UIView { ...@@ -228,27 +168,6 @@ class HomePayView:UIView {
make.width.equalTo(285.RW()) make.width.equalTo(285.RW())
make.height.equalTo(142.RW()) make.height.equalTo(142.RW())
} }
//
// let analysis = UILabel()
// analysis.text = "Analysis completed"
// analysis.textColor = UIColor.colorWithHex(hexStr: "#B3B3B3")
// analysis.font = UIFont.scaledSystemFont(ofSize: 14, weight: .bold)
// addSubview(analysis)
// analysis.snp.makeConstraints { make in
// make.left.equalTo(animationView.snp.left)
// make.top.equalTo(animationView.snp.bottom).offset(8)
// }
//
// memSize = UILabel()
// memSize.text = "100%"
// memSize.textColor = UIColor.colorWithHex(hexStr: "#B3B3B3")
// memSize.font = UIFont.scaledSystemFont(ofSize: 14, weight: .bold)
// addSubview(memSize)
// memSize.snp.makeConstraints { make in
// make.right.equalTo(animationView.snp.right)
// make.top.equalTo(animationView.snp.bottom).offset(8)
// }
appleLabel = UILabel() appleLabel = UILabel()
appleLabel?.text = " Secured with Apple" appleLabel?.text = " Secured with Apple"
...@@ -262,6 +181,7 @@ class HomePayView:UIView { ...@@ -262,6 +181,7 @@ class HomePayView:UIView {
payButton?.setTitleColor(UIColor.colorWithHex(hexStr: whiteColor), for: .normal) payButton?.setTitleColor(UIColor.colorWithHex(hexStr: whiteColor), for: .normal)
payButton?.titleLabel?.font = UIFont.scaledSystemFont(ofSize: 16, weight: .bold) payButton?.titleLabel?.font = UIFont.scaledSystemFont(ofSize: 16, weight: .bold)
payButton?.backgroundColor = UIColor.colorWithHex(hexStr: mColor) payButton?.backgroundColor = UIColor.colorWithHex(hexStr: mColor)
payButton?.isEnabled = false
payButton?.addTarget(self, action: #selector(payButtonClick), for: .touchUpInside) payButton?.addTarget(self, action: #selector(payButtonClick), for: .touchUpInside)
self.addSubview(payButton!) self.addSubview(payButton!)
...@@ -340,49 +260,6 @@ class HomePayView:UIView { ...@@ -340,49 +260,6 @@ class HomePayView:UIView {
self.callBack(CommonPush.change) self.callBack(CommonPush.change)
} }
// if let url = Bundle.main.url(forResource: "iap", withExtension: "gif"),
// let data = try? Data(contentsOf: url) {
// let animatedImage = FLAnimatedImage(animatedGIFData: data)
// let imageView = FLAnimatedImageView()
// imageView.animatedImage = animatedImage
// imageView.startAnimating()
// addSubview(imageView)
//
// imageView.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel2!.snp.bottom).offset(40)
// make.width.equalTo(285.RW())
// make.height.equalTo(142.RW())
// }
// }
// let gifImageView = UIImageView()
// gifImageView.contentMode = .scaleAspectFit
// addSubview(gifImageView)
// gifImageView.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel2!.snp.bottom).offset(40)
// make.width.equalTo(285.RW())
// make.height.equalTo(142.RW())
// }
//
// // 从 Bundle 加载 GIF 文件
// guard let gifImage = try? UIImage(gifName: "iap.gif") else {
// print("Failed to load GIF")
// return
// }
//
// gifImageView.setGifImage(gifImage, loopCount: -1)
// gifImageView.startAnimatingGif()
// gifImageView.startAnimatingGif()
// // 设置 GIF 到 ImageView 并开始播放
// do {
// try gifImageView.setGifImage(gifImage, loopCount: 0) // 0 表示无限循环
// } catch {
// print("Error setting GIF: \(error)")
// }
} }
...@@ -438,6 +315,13 @@ class HomePayView:UIView { ...@@ -438,6 +315,13 @@ class HomePayView:UIView {
self.weekProduct = week self.weekProduct = week
self.lifeProduct = life self.lifeProduct = life
if week != nil,life != nil{
payButton?.isEnabled = true
}else{
payButton?.isEnabled = false
}
payDueView.reloadUI(type, week: week, life: life) payDueView.reloadUI(type, week: week, life: life)
trailTitle.attributedText = nil trailTitle.attributedText = nil
...@@ -537,7 +421,7 @@ class HomePayView:UIView { ...@@ -537,7 +421,7 @@ class HomePayView:UIView {
} }
trailTitle = UILabel() trailTitle = UILabel()
trailTitle.text = "Free for 7 days, then $6.99/week" trailTitle.text = "" //"Free for 7 days, then $6.99/week"
trailTitle.textColor = UIColor.colorWithHex(hexStr: black3Color) trailTitle.textColor = UIColor.colorWithHex(hexStr: black3Color)
trailTitle.font = UIFont.scaledSystemFont(ofSize: 12, weight: .regular) trailTitle.font = UIFont.scaledSystemFont(ofSize: 12, weight: .regular)
tipsView.addSubview(trailTitle) tipsView.addSubview(trailTitle)
...@@ -552,46 +436,7 @@ class HomePayView:UIView { ...@@ -552,46 +436,7 @@ class HomePayView:UIView {
make.left.right.equalToSuperview().inset(12) make.left.right.equalToSuperview().inset(12)
make.height.equalTo(110.RH()) make.height.equalTo(110.RH())
} }
// let freespace = UILabel()
// freespace.font = UIFont.scaledSystemFont(ofSize: 12, weight: .bold)
// freespace.text = "FREE SPACE"
// freespace.textColor = UIColor.colorWithHex(hexStr: "#B3B3B3")
// addSubview(freespace)
//
// sizeLabel = UILabel()
// sizeLabel.text = "54.2 GB"
// sizeLabel.textColor = UIColor.colorWithHex(hexStr: black3Color)
// sizeLabel.font = UIFont.scaledSystemFont(ofSize: 12, weight: .bold)
// addSubview(sizeLabel)
//
// freespace.snp.makeConstraints { make in
// make.left.equalTo(icloudImage!.snp.right).offset(24.RW())
// make.top.equalTo(titleLabel2!.snp.bottom).offset(42)
// make.height.equalTo(17.RW())
// }
//
// sizeLabel.snp.makeConstraints { make in
// make.top.equalTo(freespace.snp.bottom).offset(0)
// make.left.equalTo(icloudImage!.snp.right).offset(24)
// make.height.equalTo(39.RW())
// }
//
// let fullText = FileTool().formatBytes(FileTool().getStorageInfo(for: .free) ?? 0 )
// // 创建一个可变的富文本字符串
// let attributedString = NSMutableAttributedString(string: fullText)
// // 设置前半部分(动态数值)的范围
// let regularFont = UIFont.scaledSystemFont(ofSize: 28, weight: .bold) // 设置常规字体大小
// attributedString.addAttribute(.font, value: regularFont, range: NSRange.init(location: 0,length:fullText.count-2))
//
// // 设置“GB”部分的范围并将字体大小设置为12
// let smallFont = UIFont.scaledSystemFont(ofSize: 12, weight: .bold) // 设置字体大小为12
// attributedString.addAttribute(.font, value: smallFont, range:NSRange.init(location: fullText.count-2, length: 2))
//
// // 将富文本赋值给UILabel
// sizeLabel.attributedText = attributedString
payDueView.snp.makeConstraints { make in payDueView.snp.makeConstraints { make in
make.left.right.equalToSuperview() make.left.right.equalToSuperview()
make.top.equalTo(tipsView.snp.bottom).offset(90.RH()) make.top.equalTo(tipsView.snp.bottom).offset(90.RH())
...@@ -631,4 +476,17 @@ class HomePayView:UIView { ...@@ -631,4 +476,17 @@ class HomePayView:UIView {
addSubview(payDueView) addSubview(payDueView)
return payDueView return payDueView
}() }()
func setPayAnime(){
// 添加呼吸动画
let breathAnimation = CABasicAnimation(keyPath: "transform.scale")
breathAnimation.fromValue = 1.0
breathAnimation.toValue = 1.05
breathAnimation.duration = 0.8
breathAnimation.autoreverses = true
breathAnimation.repeatCount = Float.infinity
breathAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
payButton?.layer.add(breathAnimation, forKey: "breathingAnimation")
}
} }
//
// PayDistanceAnimaeView.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
class PayDistanceAnimaeView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
...@@ -10,6 +10,7 @@ import StoreKit ...@@ -10,6 +10,7 @@ import StoreKit
import SVProgressHUD import SVProgressHUD
class HomePayViewController:UIViewController { class HomePayViewController:UIViewController {
private var homePayView:HomePayView? private var homePayView:HomePayView?
...@@ -121,7 +122,7 @@ extension HomePayViewController { ...@@ -121,7 +122,7 @@ extension HomePayViewController {
IAPManager.share.fetchProducts { [weak self] products in IAPManager.share.fetchProducts { [weak self] products in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
if let (weekProduct, lifetimeProduct) = products { if let (weekProduct, lifetimeProduct,_) = products {
weakSelf.homePayView?.reloadSKPorduct(week: weekProduct, life: lifetimeProduct) weakSelf.homePayView?.reloadSKPorduct(week: weekProduct, life: lifetimeProduct)
} }
} }
...@@ -129,7 +130,7 @@ extension HomePayViewController { ...@@ -129,7 +130,7 @@ extension HomePayViewController {
} }
private func payTouch() -> Void { private func payTouch() -> Void {
IAPManager.share.purchase((homePayView?.type == 0) ? .subscribe : .nonConsumable) {[weak self] result in IAPManager.share.purchase((homePayView?.type == 0) ? .weekSubscribe : .nonConsumable) {[weak self] result in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
switch result { switch result {
case .success(let success): case .success(let success):
...@@ -182,4 +183,5 @@ extension HomePayViewController { ...@@ -182,4 +183,5 @@ extension HomePayViewController {
rt.present(nav, animated: true) rt.present(nav, animated: true)
} }
} }
//
// PayDistanceViewController.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
import StoreKit
import SVProgressHUD
import Lottie
class PayDistanceViewController: UIViewController {
var scrollView:UIScrollView!
var dropImage:UIImageView!
var yearDistacePrice:UILabel!
var payBtn:UIButton!
var year:SKProduct?
var life:SKProduct?
override func viewDidLoad() {
super.viewDidLoad()
configUI()
reloadSKPorduct()
IAPManager.share.showYearPage = false
}
func configUI(){
scrollView = UIScrollView()
scrollView.frame = CGRect(x: 0, y: 0, width: ScreenW, height: ScreenH)
scrollView.contentSize = CGSize(width: ScreenW, height: ScreenH)
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.delaysContentTouches = false // 添加这一行
scrollView.canCancelContentTouches = true // 添加这一行
view.addSubview(scrollView)
let closeBtn = UIButton()
closeBtn.setImage(UIImage.init(named: "home_pay_close"), for: .normal)
closeBtn.addTarget(self, action: #selector(payClose), for: .touchUpInside)
scrollView.addSubview(closeBtn)
let offerImage = UIImageView()
offerImage.image = UIImage.init(named: "iap_distance_offer")
scrollView.addSubview(offerImage)
dropImage = UIImageView()
dropImage.image = UIImage.init(named: "iap_distance_bg")
scrollView.addSubview(dropImage)
let distanceL = UILabel()
distanceL.text = "70% Discount"
distanceL.font = UIFont.systemFont(ofSize: 40, weight: .semibold)
distanceL.textColor = UIColor.colorWithHex(hexStr: "#111111")
scrollView.addSubview(distanceL)
let annual = UILabel()
annual.text = "Annual visit"
annual.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
annual.textColor = UIColor.colorWithHex(hexStr: "#111111")
scrollView.addSubview(annual)
yearDistacePrice = UILabel()
yearDistacePrice.font = UIFont.systemFont(ofSize: 14,weight: .semibold)
yearDistacePrice.textColor = UIColor.colorWithHex(hexStr: "#111111")
yearDistacePrice.text = ""//"$39.99 $19.99 / Year"
scrollView.addSubview(yearDistacePrice)
// let priceAtt = createRichPriceText(
// originalPrice: "$39.99",
// discountedPrice: "$19.99 / Year"
// )
// yearDistacePrice.attributedText = priceAtt
let cancelLabel = UILabel()
cancelLabel.font = UIFont.systemFont(ofSize: 12,weight: .semibold)
cancelLabel.textColor = UIColor.colorWithHex(hexStr: "#0082FF")
cancelLabel.text = "Cancel at any time"
scrollView.addSubview(cancelLabel)
payBtn = UIButton()
payBtn.setTitle("Get discounts", for: .normal)
payBtn.setTitleColor(.white, for: .normal)
payBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
payBtn.layer.cornerRadius = 30
payBtn.isEnabled = false
payBtn.addTarget(self, action: #selector(iapPayAction), for: .touchUpInside)
payBtn.backgroundColor = UIColor.colorWithHex(hexStr: "#0082FF")
scrollView.addSubview(payBtn)
let termsBtn = UIButton()
termsBtn.setTitle("Terms", for: .normal)
termsBtn.titleLabel?.font = UIFont.systemFont(ofSize: 11.RW(),weight: .medium)
termsBtn.setTitleColor(UIColor.colorWithHex(hexStr: "#999999"), for: .normal)
termsBtn.titleLabel?.addUnderline()
termsBtn.addTarget(self, action: #selector(termsAction(btn:)), for: .touchUpInside)
scrollView.addSubview(termsBtn)
let recoveryBtn = UIButton()
recoveryBtn.setTitle("Restore", for: .normal)
recoveryBtn.titleLabel?.font = UIFont.systemFont(ofSize: 11.RW(),weight: .medium)
recoveryBtn.setTitleColor(UIColor.colorWithHex(hexStr: "#999999"), for: .normal)
recoveryBtn.frame = CGRect(x: scrollView.bounds.width - 66, y: ScreenH - (20+kSafeAreaInsets.bottom), width: CGFloat(50.RW()), height: 20)
recoveryBtn.titleLabel?.addUnderline()
recoveryBtn.addTarget(self, action: #selector(recoveryBtnAction), for: .touchUpInside)
scrollView.addSubview(recoveryBtn)
let appleLabel = UILabel()
appleLabel.font = UIFont.systemFont(ofSize: 10,weight: .medium)
appleLabel.textColor = UIColor.colorWithHex(hexStr: "#111111")
appleLabel.text = " Apple Security Guarantee"
scrollView.addSubview(appleLabel)
closeBtn.snp.makeConstraints { make in
make.left.equalTo(16)
make.top.equalTo(kSafeAreaInsets.top+20)
make.size.equalTo(30)
}
offerImage.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(kSafeAreaInsets.top+70)
make.width.equalTo(111)
make.height.equalTo(37)
}
distanceL.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(annual.snp.top).offset(-115.RH())
}
appleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.height.equalTo(20)
make.top.equalToSuperview().offset(ScreenH - (20+kSafeAreaInsets.bottom))
}
termsBtn.snp.makeConstraints { make in
make.centerY.equalTo(appleLabel)
make.left.equalTo(16)
}
payBtn.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.equalTo(343)
make.height.equalTo(57)
make.bottom.equalTo(appleLabel.snp.top).offset(-24)
}
cancelLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(payBtn.snp.top).offset(-22)
make.height.equalTo(22)
}
yearDistacePrice.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(cancelLabel.snp.top).offset(0)
make.height.equalTo(22)
}
annual.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(yearDistacePrice.snp.top).offset(-8)
make.height.equalTo(22)
}
scrollView.addSubview(termsView)
termsView.addSubview(privavye_Label)
termsView.snp.makeConstraints { make in
make.left.equalTo(scrollView).offset(16)
make.right.equalTo(scrollView).offset(-16)
make.top.equalTo(termsBtn.snp.bottom).offset(15.RH())
make.height.equalTo(500.RW())
// 添加这一行确保 termsView 的宽度正确
make.width.equalTo(scrollView).offset(-32)
}
privavye_Label.snp.makeConstraints { make in
make.edges.equalToSuperview()
// 添加宽度约束确保 label 能正确计算其尺寸
make.width.equalToSuperview()
}
view.addSubview(animationView)
animationView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
animationView.play { completed in
if completed{
self.animationView.removeFromSuperview()
self.setAnim()
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func setAnim(){
let dropW = CGFloat(300.RW())
let dropH = CGFloat(224.RW())
dropImage.frame = CGRect(x: (ScreenW - dropW) / 2, y: -dropH, width: dropW, height: dropH)
// 添加重力动画效果
UIView.animate(withDuration: 1.1,
delay: 0.2,
usingSpringWithDamping: 0.5, // 弹性系数,越小弹性越大
initialSpringVelocity: 0.8, // 初始速度
options: .curveEaseIn,
animations: {
// 设置最终位置
self.dropImage.frame = CGRect(x: (ScreenW - dropW)/2.0,
y: kSafeAreaInsets.top + 129.RH(), // 根据原来的约束位置计算
width: dropW,
height: dropH)
}, completion: nil)
// 添加呼吸动画
let breathAnimation = CABasicAnimation(keyPath: "transform.scale")
breathAnimation.fromValue = 1.0
breathAnimation.toValue = 1.05
breathAnimation.duration = 0.8
breathAnimation.autoreverses = true
breathAnimation.repeatCount = Float.infinity
breathAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
payBtn.layer.add(breathAnimation, forKey: "breathingAnimation")
}
func reloadSKPorduct(){
IAPManager.share.fetchProducts { [weak self] products in
guard let weakSelf = self else { return }
DispatchQueue.main.async {
if let (_,lifetimeProduct,yearProduct) = products {
weakSelf.year = yearProduct
weakSelf.life = lifetimeProduct
weakSelf.reloadSKUI()
}
}
}
}
func reloadSKUI(){
guard let year = year,let life = life else{
payBtn.isEnabled = false
return
}
payBtn.isEnabled = true
let priceAtt = createRichPriceText(
originalPrice: "\(life.localizedPrice)",
discountedPrice: "\(year.localizedPrice) / Year"
)
yearDistacePrice.attributedText = priceAtt
}
lazy var termsView:UIView = {
let termsView = UIView()
termsView.backgroundColor = .clear
return termsView
}()
lazy var privavye_Label: UILabel = {
let priva = UILabel()
priva.font = UIFont.scaledSystemFont(ofSize: 14, weight: .regular)
priva.textColor = .gray
priva.text = ""
priva.numberOfLines = 0
priva.clipsToBounds = true
priva.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
tap.delegate = self
tap.cancelsTouchesInView = false // 添加这一行
priva.addGestureRecognizer(tap)
return priva
}()
lazy var animationView:LottieAnimationView = {
let animationView = LottieAnimationView(name: "discount")
animationView.loopMode = .playOnce
animationView.contentMode = .scaleAspectFill
return animationView
}()
}
extension PayDistanceViewController{
@objc func payClose(){
self.dismiss(animated: true)
}
@objc func iapPayAction(){
IAPManager.share.purchase(.yearSubscribe) {[weak self] result in
guard let weakSelf = self else { return }
switch result {
case .success(_):
DispatchQueue.main.async {
SVProgressHUD.showSuccess(withStatus: "purchase succeeds")
weakSelf.dismiss(animated: true)
}
case .failure(let failure):
DispatchQueue.main.async {
SVProgressHUD.showError(withStatus: failure.localizedDescription)
}
}
}
}
func createRichPriceText(originalPrice: String, discountedPrice: String) -> NSAttributedString {
// 创建可变属性字符串
let attributedString = NSMutableAttributedString()
// 1. 设置原价部分(带删除线)
let originalPriceAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.colorWithHex(hexStr: "#111111", alpha: 0.6),
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
.font: UIFont.systemFont(ofSize: 14,weight: .semibold)
]
let originalPricePart = NSAttributedString(string: originalPrice, attributes: originalPriceAttributes)
attributedString.append(originalPricePart)
// 2. 添加空格分隔
attributedString.append(NSAttributedString(string: " "))
// 3. 设置折扣价部分(无删除线)
let discountedPriceAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.colorWithHex(hexStr: "#111111"),
.font: UIFont.systemFont(ofSize: 14, weight: .semibold)
]
let discountedPricePart = NSAttributedString(string: discountedPrice, attributes: discountedPriceAttributes)
attributedString.append(discountedPricePart)
return attributedString
}
@objc func termsAction(btn:UIButton){
btn.isSelected = !btn.isSelected
let strs:NSString = btn.isSelected ? "We offer 1-year and lifetime subscriptions. The price is clearly displayed within the application.\n\n·After the purchase confirmation, your iTunes account will be charged.\n\n·Unless the automatic update is turned off at least 24 hours before the end of the current cycle, the subscription will be automatically updated.\n\n·Your account will be renewed within 24 hours before the end of the current cycle cost.\n\n·You can go to the \"Account Settings\" in the iTunes store to manage your subscriptions and turn off auto renewal.\n\n·If provided, if you choose to use our free trial version, any unused portion during the free trial period will become invalid when you purchase a publication subscription, if applicable.\n\n·If you choose not to purchase the AI PhoneManager Pro version, you can continue to use it for free and enjoy PhoneManager.\n\nYour personal data is securely stored in PhoneManager, please make sure to read our \nprivacy policy and terms of use." : ""
let attribtit = NSMutableAttributedString(string:strs as String , attributes: [:])
if strs.length > 3 {
let rang = strs.range(of: "privacy policy")
attribtit.addAttributes([NSAttributedString.Key.font : UIFont.scaledSystemFont(ofSize: 14, weight: .bold),NSAttributedString.Key.underlineStyle:NSUnderlineStyle.single.rawValue,.link:"appscheme://private"], range: rang)
let rang1 = strs.range(of: "terms of use")
attribtit.addAttributes([NSAttributedString.Key.font : UIFont.scaledSystemFont(ofSize: 14, weight: .bold),NSAttributedString.Key.underlineStyle:NSUnderlineStyle.single.rawValue,.link:"appscheme://terms"], range: rang1)
}
let contentH = attribtit.length > 0 ? 500.RW() : 0
// self.scrollView.contentSize = CGSizeMake(ScreenW, contentH + ScreenH)
self.scrollView.contentSize = CGSize(width: self.scrollView.frame.width, height: contentH + ScreenH)
privavye_Label.attributedText = attribtit
// privavye_Label.frame = CGRect(x: 16, y: ScreenH+20, width: ScreenW-32, height: contentH)
Print("scrollView frame:", scrollView.frame)
Print("scrollView contentSize:", scrollView.contentSize)
Print("termsView frame:", termsView.frame)
Print("privavye_Label frame:", privavye_Label.frame)
// view.layoutIfNeeded()
// if privavye_Label.attributedText?.length ?? 0 > 10 {
// UIView.animate(withDuration: 0.1) {
// self.scrollView.contentOffset = CGPoint(x: 0, y: self.scrollView.contentSize.height - self.scrollView.height)
// }
// }
}
@objc private func labelTapped(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: privavye_Label)
let strs:NSString = "privacy policy"
let frssss = strs.boundingRect(with: CGSizeMake(.infinity, .infinity), options: .truncatesLastVisibleLine, attributes: [NSAttributedString.Key.font : privavye_Label.font ?? UIFont.scaledSystemFont(ofSize: 14, weight: .regular)], context: nil)
let frame1 = CGRectMake(0, privavye_Label.height-25, CGRectGetWidth(frssss), 25)
let frame2 = CGRectMake(CGRectGetWidth(frssss)+20, privavye_Label.height-25, 90, 25)
if frame1.contains(location) {
Print("点击了隐私")
ppClick()
}else if frame2.contains(location){
Print("点击了terms")
touClick()
}
}
private func ppClick() {
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:PrivacyPolicyWebViewController = PrivacyPolicyWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
private func touClick() {
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:TermOfUseWebViewController = TermOfUseWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
@objc func recoveryBtnAction(){
IAPManager.share.restore { result in
}
}
}
// 在文件末尾添加
extension PayDistanceViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 允许手势同时识别
return true
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<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" customClass="PayDistanceViewController" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
</view>
</objects>
</document>
...@@ -11,20 +11,20 @@ ...@@ -11,20 +11,20 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="EwM-B0-fXo" customClass="PMPermissionView" customModule="PhoneManager" customModuleProvider="target"> <view contentMode="scaleToFill" id="EwM-B0-fXo" customClass="PMPermissionView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="543" height="449"/> <rect key="frame" x="0.0" y="0.0" width="470" height="337"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_queshengtu" translatesAutoresizingMaskIntoConstraints="NO" id="qhV-IF-vsx"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_queshengtu" translatesAutoresizingMaskIntoConstraints="NO" id="qhV-IF-vsx">
<rect key="frame" x="177" y="100" width="189" height="189"/> <rect key="frame" x="140.66666666666666" y="0.0" width="188.99999999999997" height="189"/>
</imageView> </imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Access permission is required to start scan" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ci8-h6-fiE"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Access permission is required to start scan" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ci8-h6-fiE">
<rect key="frame" x="5" y="297" width="533" height="20"/> <rect key="frame" x="5" y="197" width="460" height="20"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone Manager We need access to all the photos in your photo library" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e8v-Xy-wvP"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone Manager We need access to all the photos in your photo library" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e8v-Xy-wvP">
<rect key="frame" x="109" y="320" width="325" height="38.333333333333314"/> <rect key="frame" x="72.666666666666686" y="220" width="325" height="38.333333333333314"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="325" id="7Q3-fW-f7G"/> <constraint firstAttribute="width" constant="325" id="7Q3-fW-f7G"/>
</constraints> </constraints>
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fRi-wi-J8I"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fRi-wi-J8I">
<rect key="frame" x="177" y="371.33333333333331" width="189" height="46"/> <rect key="frame" x="140.66666666666666" y="271.33333333333331" width="188.99999999999997" height="46"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="46" id="2ml-bk-VZ7"/> <constraint firstAttribute="height" constant="46" id="2ml-bk-VZ7"/>
...@@ -59,14 +59,14 @@ ...@@ -59,14 +59,14 @@
<constraint firstItem="ci8-h6-fiE" firstAttribute="leading" secondItem="Z86-W8-437" secondAttribute="leading" constant="5" id="2pS-Hl-sgb"/> <constraint firstItem="ci8-h6-fiE" firstAttribute="leading" secondItem="Z86-W8-437" secondAttribute="leading" constant="5" id="2pS-Hl-sgb"/>
<constraint firstItem="ci8-h6-fiE" firstAttribute="centerX" secondItem="qhV-IF-vsx" secondAttribute="centerX" id="Fv3-iO-5BI"/> <constraint firstItem="ci8-h6-fiE" firstAttribute="centerX" secondItem="qhV-IF-vsx" secondAttribute="centerX" id="Fv3-iO-5BI"/>
<constraint firstItem="e8v-Xy-wvP" firstAttribute="centerX" secondItem="ci8-h6-fiE" secondAttribute="centerX" id="OZD-Bq-U2s"/> <constraint firstItem="e8v-Xy-wvP" firstAttribute="centerX" secondItem="ci8-h6-fiE" secondAttribute="centerX" id="OZD-Bq-U2s"/>
<constraint firstItem="qhV-IF-vsx" firstAttribute="top" secondItem="EwM-B0-fXo" secondAttribute="top" constant="100" id="fx5-wC-f0q"/> <constraint firstItem="qhV-IF-vsx" firstAttribute="top" secondItem="EwM-B0-fXo" secondAttribute="top" id="fx5-wC-f0q"/>
<constraint firstItem="fRi-wi-J8I" firstAttribute="top" secondItem="e8v-Xy-wvP" secondAttribute="bottom" constant="13" id="jys-rP-uUI"/> <constraint firstItem="fRi-wi-J8I" firstAttribute="top" secondItem="e8v-Xy-wvP" secondAttribute="bottom" constant="13" id="jys-rP-uUI"/>
<constraint firstItem="e8v-Xy-wvP" firstAttribute="top" secondItem="ci8-h6-fiE" secondAttribute="bottom" constant="3" id="tgi-kq-3Ap"/> <constraint firstItem="e8v-Xy-wvP" firstAttribute="top" secondItem="ci8-h6-fiE" secondAttribute="bottom" constant="3" id="tgi-kq-3Ap"/>
<constraint firstItem="ci8-h6-fiE" firstAttribute="top" secondItem="qhV-IF-vsx" secondAttribute="bottom" constant="8" id="uKW-kw-KCJ"/> <constraint firstItem="ci8-h6-fiE" firstAttribute="top" secondItem="qhV-IF-vsx" secondAttribute="bottom" constant="8" id="uKW-kw-KCJ"/>
<constraint firstItem="Z86-W8-437" firstAttribute="trailing" secondItem="ci8-h6-fiE" secondAttribute="trailing" constant="5" id="wMs-cZ-eGL"/> <constraint firstItem="Z86-W8-437" firstAttribute="trailing" secondItem="ci8-h6-fiE" secondAttribute="trailing" constant="5" id="wMs-cZ-eGL"/>
</constraints> </constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="295.41984732824426" y="-329.22535211267609"/> <point key="canvasLocation" x="239.69465648854961" y="-368.66197183098592"/>
</view> </view>
</objects> </objects>
<resources> <resources>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -70,4 +70,8 @@ struct FileTool{ ...@@ -70,4 +70,8 @@ struct FileTool{
return totalSize return totalSize
} }
} }
...@@ -296,6 +296,22 @@ class PhotoAndVideoMananger { ...@@ -296,6 +296,22 @@ class PhotoAndVideoMananger {
} }
} }
func asynGetImageFromAssetID(id: String,finised:@escaping (UIImage)->Void) {
if let asset = getPHAsssetwithID(ids: [id]) {
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 400, height: 400)
let options = PHImageRequestOptions()
options.isSynchronous = false
options.deliveryMode = .opportunistic
options.isNetworkAccessAllowed = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { result, _ in
finised(result ?? UIImage())
}
}else {
finised(UIImage())
}
}
func getImageFromAsset(asset: PHAsset) -> UIImage? { func getImageFromAsset(asset: PHAsset) -> UIImage? {
var image: UIImage? var image: UIImage?
let imageManager = PHImageManager.default() let imageManager = PHImageManager.default()
......
...@@ -85,3 +85,11 @@ public func Alert( _ title:String? , _ message:String) -> Void { ...@@ -85,3 +85,11 @@ public func Alert( _ title:String? , _ message:String) -> Void {
alert.addAction(UIAlertAction(title: "OK", style: .cancel)) alert.addAction(UIAlertAction(title: "OK", style: .cancel))
cWindow?.rootViewController?.present(alert, animated: true) cWindow?.rootViewController?.present(alert, 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)
}
//
// FadeOutTransition.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import Foundation
import UIKit
// 自定义转场动画(用于消失时的渐隐效果)
class FadeOutTransition: NSObject, UIViewControllerAnimatedTransitioning {
// 动画时长(可自定义)
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3 // 0.3秒渐隐
}
// 执行动画
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 获取即将消失的控制器(被弹出的控制器)
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else { return }
// 将目标控制器的视图添加到容器视图(确保转场后界面正确)
transitionContext.containerView.addSubview(toVC.view)
// 渐隐动画:将 fromVC 的视图透明度从 1 降到 0
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromVC.view.alpha = 0
}) { (_) in
// 动画完成后,标记转场结束
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
// 自定义转场动画(用于 pop 时的渐隐效果)
class FadeOutPopTransition: NSObject, UIViewControllerAnimatedTransitioning {
// 动画时长
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3 // 0.3秒渐隐
}
// 执行动画
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 获取当前视图控制器(即将消失的控制器)
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else { return }
// 将目标控制器(RootViewController)的视图添加到容器视图
transitionContext.containerView.addSubview(toVC.view)
// 渐隐动画:当前控制器视图透明度从 1 降到 0
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromVC.view.alpha = 0
}) { (_) in
// 动画完成后,标记转场结束
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
...@@ -15,4 +15,8 @@ extension NSNotification.Name { ...@@ -15,4 +15,8 @@ extension NSNotification.Name {
//监听拿到基本相册资源 //监听拿到基本相册资源
static let getBaseAssetsSuccess: NSNotification.Name = NSNotification.Name(rawValue: "getBaseAssetsSuccess") static let getBaseAssetsSuccess: NSNotification.Name = NSNotification.Name(rawValue: "getBaseAssetsSuccess")
//首次打开引导页关闭
static let guidePageClose: NSNotification.Name = NSNotification.Name(rawValue: "guidePageClose")
} }
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