Commit 9febb62f authored by CZ1004's avatar CZ1004

Merge branch 'dev_main' into dev_zhaoqian

* dev_main:
  处理退款,修复保留bug,首页视频播放
  优化图片加载器
  订阅增加类型获取,新增年费会员
  年费会员界面,启动引导页结构更改
parents 78402c67 10caad0a
......@@ -27,13 +27,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.backgroundColor = .white
window?.overrideUserInterfaceStyle = .light
let Ssoryboard = UIStoryboard(name: "LauchVC", bundle: nil)
if let current = Ssoryboard.instantiateViewController(identifier: "LauchVCID") as? LauchVC {
window?.rootViewController = current
window?.makeKeyAndVisible()
}
let rootNav = BaseNavViewController(rootViewController: HomeViewController())
window?.rootViewController = rootNav
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 storage = WidgetPublicModel.UseDiskSpace() * 100
widgetAppgourp.share.PushWidgetData(battery: Int(battery), storage: Int(storage))
......@@ -52,6 +58,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// 相册基本资源加载
PhotoManager.shared.config()
// 设置app动态按钮
setupDynamicShortcuts()
return true
}
......@@ -128,20 +136,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private func setupDefault() {
IAPManager.share.checkSubscriptionState { isSubscribed, expiresDate in
Print("是否内购---",isSubscribed)
}
NetStatusManager.manager.startNet { status in
switch status {
case .NoNet:
break
case .WIFI,.WWAN:
if (PhotoAndVideoMananger.mananger.allAssets.count == 0) {
PhotoAndVideoMananger.mananger.setAssets()
}
break
}
}
......
//
// 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" : [
{
"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 {
}
}
// 添加一个枚举来表示订阅类型
enum SubscriptionType {
case none
case week
case year
case lifetime
}
// MARK: - IAPManager
class IAPManager: NSObject {
......@@ -48,7 +57,8 @@ class IAPManager: NSObject {
private let alert = PMLoadingHUD.share
enum PayState {
case subscribe
case weekSubscribe
case yearSubscribe
case nonConsumable
}
......@@ -56,9 +66,10 @@ class IAPManager: NSObject {
private struct ProductID {
static let weekMember = "com.app.phonemanager.week.member"
static let lifetimeMember = "com.app.phonemanager.lifetime.member"
static let yearMember = "com.app.phonemanager.year.member"
static var all: [String] {
return [weekMember, lifetimeMember]
return [weekMember, lifetimeMember,yearMember]
}
}
......@@ -66,14 +77,16 @@ class IAPManager: NSObject {
private var weekProduct: 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 restoreCompletion: ((Result<Bool, IAPError>) -> Void)?
//获取商品信息的完成回调
private var productRequestCompletion: (((SKProduct?,SKProduct?)?) -> Void)?
private var productRequestCompletion: (((SKProduct?,SKProduct?,SKProduct?)?) -> Void)?
var isSubscribed = false {
didSet {
......@@ -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
private override init() {
super.init()
SKPaymentQueue.default().add(self)
}
deinit {
......@@ -98,15 +124,16 @@ class IAPManager: NSObject {
extension IAPManager {
// 获取订阅内购商品信息
func fetchProducts(completion: @escaping ((SKProduct?,SKProduct?)?) -> Void) {
func fetchProducts(completion: @escaping ((SKProduct?,SKProduct?,SKProduct?)?) -> Void) {
// 先检查缓存
if let cachedProducts = getCachedProducts() {
// 后台更新最新数据
self.weekProduct = cachedProducts.filter{$0.productIdentifier == ProductID.weekMember}.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()
return
}
productRequestCompletion = completion
......@@ -128,38 +155,69 @@ extension IAPManager {
// 检查订阅信息
/// - 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
switch result {
case .success(let receipt):
// 打印完整收据信息,方便调试
//print("收据信息:\(receipt)")
// print("收据信息:\(receipt)")
let status = self.checkSubscriptionStatus(receiptInfo: receipt)
DispatchQueue.main.async {
completion(status.isActive, status.expiresDate)
// 更新本地订阅状态
completion(status.isActive, status.subscriptionType, status.expiresDate)
self.isSubscribed = status.isActive
self.subscriptionType = status.subscriptionType
#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))")
Print("订阅状态:\(status.isActive ? "已订阅" : "未订阅")订阅类型:\(status.subscriptionType)到期时间:\(formatter.string(from: expDate))")
}
#endif
}
case .failure(_):
// 处理错误情况
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))
return
}
......@@ -175,12 +233,23 @@ extension IAPManager {
self.state = state
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)
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.restoreCompletion = completion
SKPaymentQueue.default().restoreCompletedTransactions()
......@@ -384,15 +453,15 @@ extension IAPManager: SKProductsRequestDelegate {
// 拿到请求下来的商品信息
self.weekProduct = products.filter{$0.productIdentifier == ProductID.weekMember}.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{
cacheProducts([week,life])
if let week = self.weekProduct,let life = self.lifetimeProduct,let year = self.yearProduct{
cacheProducts([week,life,year])
}
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else { return }
self?.productRequestCompletion?((weakSelf.weekProduct,weakSelf.lifetimeProduct))
self?.productRequestCompletion?((weakSelf.weekProduct,weakSelf.lifetimeProduct,weakSelf.yearProduct))
BackgroundTaskManager.share.endTask()
}
}
......@@ -419,7 +488,7 @@ extension IAPManager: SKPaymentTransactionObserver {
/** 恢复购买 */
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
// 刷新订阅状态
checkSubscriptionState { [weak self] isSubscribed, expiresDate in
checkSubscriptionState { [weak self] isSubscribed,type,expiresDate in
guard let self = self else { return }
// 更新恢复购买的结果
if isSubscribed {
......@@ -447,55 +516,118 @@ extension IAPManager: SKPaymentTransactionObserver {
extension IAPManager{
func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, expiresDate: Date?) {
// 首先检查 in_app 购买记录
if let receipt = receiptInfo["receipt"] as? [String: Any],
let inAppPurchases = receipt["in_app"] as? [[String: Any]] {
// 检查是否存在永久商品购买记录
let hasLifetimePurchase = inAppPurchases.contains { purchase in
return purchase["product_id"] as? String == ProductID.lifetimeMember
}
if hasLifetimePurchase {
print("检测到永久商品购买记录")
return (true, false, nil)
}
}
// 如果没有永久商品,继续检查订阅
guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else {
print("没有找到任何购买记录")
return (false, false, 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, nil)
}
// 解析试用期状态
let isTrial = (lastReceipt["is_trial_period"] as? String) == "true"
// 获取过期时间
let expiresDate = getExpiryDate(from: lastReceipt)
let isActive = expiresDate?.compare(Date()) == .orderedDescending
#if DEBUG
if let expDate = expiresDate {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print("订阅状态:\(isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))")
}
#endif
return (isActive, isTrial, expiresDate)
}
// func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, expiresDate: Date?) {
// // 首先检查 in_app 购买记录
// if let receipt = receiptInfo["receipt"] as? [String: Any],
// let inAppPurchases = receipt["in_app"] as? [[String: Any]] {
// // 检查是否存在永久商品购买记录
// let hasLifetimePurchase = inAppPurchases.contains { purchase in
// return purchase["product_id"] as? String == ProductID.lifetimeMember
// }
//
// if hasLifetimePurchase {
// print("检测到永久商品购买记录")
// return (true, false, nil)
// }
// }
//
// // 如果没有永久商品,继续检查订阅
// guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else {
// print("没有找到任何购买记录")
// return (false, false, 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, nil)
// }
//
// // 解析试用期状态
// let isTrial = (lastReceipt["is_trial_period"] as? String) == "true"
//
// // 获取过期时间
// let expiresDate = getExpiryDate(from: lastReceipt)
// let isActive = expiresDate?.compare(Date()) == .orderedDescending
//
// #if DEBUG
// if let expDate = expiresDate {
// let formatter = DateFormatter()
// formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
// print("订阅状态:\(isActive ? "已订阅" : "未订阅"),到期时间:\(formatter.string(from: expDate))")
// }
// #endif
//
// 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? {
......
......@@ -2,7 +2,12 @@ import UIKit
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 {
// 关联对象键,用于存储当前请求的 localIdentifier
......@@ -34,7 +39,7 @@ extension UIImageView {
// 加载图片的主方法
func loadImage(
withLocalIdentifier identifier: String,
targetSize: CGSize = PHImageManagerMaximumSize,
targetSize: CGSize = CGSize(width: 300, height: 300),
placeholder: UIImage? = nil,
completion: ((UIImage?) -> Void)? = nil
) {
......@@ -63,13 +68,23 @@ extension UIImageView {
completion?(nil)
return
}
// 6. 配置图片请求选项
let options = PHImageRequestOptions()
options.version = .current
options.deliveryMode = .highQualityFormat
options.deliveryMode = .highQualityFormat // 改为渐进式加载
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. 请求图片
currentRequestID = PHImageManager.default().requestImage(
......@@ -116,9 +131,18 @@ extension UIImageView {
}
}
// 清理缓存
@objc func clearCacheOnMemoryWarning() {
imageCache.removeAllObjects()
}
// 命名空间
struct AssetLoader {
fileprivate weak var imageView: UIImageView?
func clearImageCache() {
imageCache.removeAllObjects()
}
}
var asset: AssetLoader {
......
......@@ -34,6 +34,12 @@ enum BaseDataLoadingState {
case failed(Error)
}
enum FileType {
case json
case plist
case all
}
class PhotoManager{
......@@ -137,9 +143,14 @@ class PhotoManager{
private func requestAuthorization(){
// 获取基础数据
requestAuthorization {[weak self] _ in
requestAuthorization {[weak self] permission in
guard let weakSelf = self else { return }
weakSelf.getBaseAssetGroup()
if permission{
weakSelf.getBaseAssetGroup()
}else{
//weakSelf.clearLocalData()
}
}
}
......@@ -204,6 +215,7 @@ class PhotoManager{
let start = CFAbsoluteTimeGetCurrent()
self.videoModels = await convertAssetsToModel(for: self.videoAssets, mediaType: 2)
self.videoModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start
print("视频转换总耗时: \(duration)秒")
let videoTotalSize = Int64(self.videoModels.reduce(0){$0+$1.assetSize})
......@@ -233,6 +245,7 @@ class PhotoManager{
let start = CFAbsoluteTimeGetCurrent()
self.otherModels = await convertAssetsToModel(for: self.otherAssets, mediaType: 1)
self.otherModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start
print("其他图片转换总耗时: \(duration)秒")
let otherTotalSize = Int64(self.otherModels.reduce(0){$0+$1.assetSize})
......@@ -258,11 +271,11 @@ class PhotoManager{
self.screenShotTotalSize = screenShotTotalSize
complectionHandler?(localModels, screenShotTotalSize)
}
return
}
let start = CFAbsoluteTimeGetCurrent()
self.screenShotModels = await convertAssetsToModel(for: self.screenShotAssets, mediaType: 1)
self.screenShotModels.sort { $0.createDate > $1.createDate }
let duration = CFAbsoluteTimeGetCurrent() - start
print("截图转换总耗时: \(duration)秒")
let screenShotTotalSize = Int64(self.screenShotModels.reduce(0){$0+$1.assetSize})
......@@ -513,7 +526,7 @@ extension PhotoManager{
options.version = .original
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
// 4. 请求视频资源
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, _, _) in
DispatchQueue.main.async {
......@@ -748,6 +761,11 @@ extension PhotoManager{
}
}
// 清楚本地存储数据
func clearLocalData(){
}
func removeAssets(withIdentifiers identifiers: [String], from assets: [AssetModel]) -> [AssetModel] {
let identifierSet = Set(identifiers)
......
......@@ -14,8 +14,12 @@ class HomeViewController:BaseViewController {
private var isShowCharge:Bool = false
private var canShowIAP:Bool = false
var homeView:HomeView?
fileprivate func junmToModule(_ cIndex: Int, _ self: HomeViewController) {
switch cIndex {
......@@ -107,7 +111,7 @@ class HomeViewController:BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadLaunchView()
self.setupUI()
// 调用下追踪权限
......@@ -181,7 +185,66 @@ class HomeViewController:BaseViewController {
view.addSubview(homeView!)
}
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()
}
}
}
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
......@@ -203,6 +266,21 @@ class HomeViewController:BaseViewController {
homeView?.viewModel.reloadTrashAndKeep()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.barHidden = false
// 开启定时器
Singleton.shared.startCountdown {}
if !isShowCharge {
NotificationManager().configNotifications()
return
}
}
}
extension HomeViewController {
......@@ -259,3 +337,4 @@ extension HomeViewController {
}
}
}
......@@ -60,10 +60,10 @@ class HomeCollectionViewHeader : UICollectionReusableView {
self.addSubview(self.permissionView)
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.right.equalToSuperview().offset(22)
make.height.equalTo(449)
make.height.equalTo(340)
}
......@@ -206,6 +206,7 @@ class CustomProgressBar: UIView {
progressLayer.frame = CGRect(x: 0, y: 0, width: ScreenW-48, height: 10)
progressLayer.cornerRadius = 5
progressLayer.masksToBounds = true
updateProgress()
}
......
......@@ -135,11 +135,4 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
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 {
sview.register(HomeTitleCollectionCell.self, forCellWithReuseIdentifier: HomeTitleCollectionCell.identifiers)
sview.register(HomeOtherCollectionCell.self, forCellWithReuseIdentifier: HomeOtherCollectionCell.identifier)
sview.register(HomeVideoCoverCell.classForCoder(), forCellWithReuseIdentifier: "HomeVideoCoverCell")
sview.register(HomeCollectionViewHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HomeCollectionViewHeader")
sview.register(UICollectionReusableView.self,forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,withReuseIdentifier: "HomeCollectionViewFooter")
......@@ -278,11 +278,19 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
return cell
case 1:
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
if viewModel.cardGroup[indexPath.row].folderName == HomeUIEnum.SimilarVideos.title || viewModel.cardGroup[indexPath.row].folderName == HomeUIEnum.Videos.title{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeVideoCoverCell", for: indexPath) as! HomeVideoCoverCell
let model = viewModel.cardGroup[indexPath.row]
cell.reloadUIWithModel(model: model)
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:
return UICollectionViewCell()
}
......@@ -402,9 +410,9 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize {
if section == 0 {
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{
return CGSize(width: self.collectionView.width, height: 404)
return CGSize(width:ScreenW-(marginLR*2), height: 450)
}
}
return CGSize.zero
......
......@@ -12,7 +12,7 @@ class HomeTitleCollectionCell:UICollectionViewCell {
static let identifiers = "HomeTitleCollectionCellID"
private var titleLabel: UILabel?
private var titleLabel: UILabel!
public var fileLabel:UILabel?
......@@ -48,8 +48,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
titleLabel = UILabel()
titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
titleLabel?.textColor = UIColor.colorWithHex(hexStr: black3Color)
titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .bold)
titleLabel.textColor = UIColor.colorWithHex(hexStr: black3Color)
fileLabel = UILabel()
fileLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
......@@ -118,8 +118,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
guard let model = model else { return }
self.model = model
titleLabel?.text = model.folderName
titleLabel?.sizeToFit()
titleLabel.text = model.folderName
titleLabel.sizeToFit()
var count = 0
......@@ -137,8 +137,8 @@ class HomeTitleCollectionCell:UICollectionViewCell {
assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model)
})
collectionView?.reloadData()
}
collectionView?.reloadData()
}
......@@ -155,12 +155,12 @@ class HomeTitleCollectionCell:UICollectionViewCell {
super.layoutSubviews()
titleLabel?.snp.makeConstraints({ make in
titleLabel.snp.makeConstraints({ make in
make.top.left.equalTo(16)
})
titleLabel?.sizeToFit()
titleLabel.sizeToFit()
fileLabel?.snp.makeConstraints({ make in
......@@ -172,9 +172,9 @@ class HomeTitleCollectionCell:UICollectionViewCell {
collectionView?.snp.makeConstraints({ make in
make.left.equalTo(16)
make.bottom.equalTo(-16)
make.bottom.equalTo(0)
make.right.equalTo(-16)
make.top.equalTo(48)
make.top.equalTo(titleLabel.snp.bottom).offset(13)
})
nextImage?.snp.makeConstraints({ make in
......@@ -205,7 +205,7 @@ extension HomeTitleCollectionCell:UICollectionViewDelegate,UICollectionViewDataS
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 计算 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 {
......
//
// 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 {
return
}
guard photoManager.permissionStatus == .authorized else{
return
}
totalFilesCount = photoManager.allAssets.count
hadLoad = true
......
......@@ -80,6 +80,7 @@ class MaintaiDetailViewController: BaseViewController {
if isSelectAll{
let ids = viewModel.souces.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
selectAsset.removeAll()
self.navigationController?.popViewController(animated: true)
}
isSelectAll = false
......@@ -175,7 +176,7 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
}
// 拿到当前在选中数据的下标
let selectIndex = selectAsset.firstIndex(where: {
var selectIndex = selectAsset.firstIndex(where: {
$0 == cell.selectAsset
})
......@@ -191,11 +192,15 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
weakSelf.selectAsset[sIndex] = selects
}else{
weakSelf.selectAsset.remove(at: sIndex)
selectIndex = nil
}
}else{
Print("没有任何选中数据")
weakSelf.selectAsset.append(selects)
selectIndex = weakSelf.selectAsset.firstIndex(where: {
$0 == selects
})
}
weakSelf.dealMutilImagePick()
}
......@@ -238,8 +243,10 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
}else{
maintaiBottomView.disMiss()
}
tableView.reloadData()
// tableView.reloadData()
// UIView.performWithoutAnimation {
// self.tableView.reloadData()
// }
}
func dealPickAssetModel(index:Int){
......
......@@ -21,10 +21,10 @@
<rect key="frame" x="0.0" y="0.0" width="269" height="253"/>
</imageView>
<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>
<constraint firstAttribute="width" constant="30" id="gGs-ha-T24"/>
<constraint firstAttribute="height" constant="30" id="mvi-bY-CFY"/>
<constraint firstAttribute="width" constant="34" id="gGs-ha-T24"/>
<constraint firstAttribute="height" constant="34" id="mvi-bY-CFY"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="icon_maintai_unselect_big"/>
......@@ -38,8 +38,8 @@
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<constraints>
<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="trailing" secondItem="oJ5-js-pjw" secondAttribute="trailing" constant="5" id="Iw9-UA-zNe"/>
<constraint firstAttribute="bottom" secondItem="oJ5-js-pjw" secondAttribute="bottom" id="BfF-Td-Tcc"/>
<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="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="dJs-Pa-MhO"/>
<constraint firstAttribute="trailing" secondItem="T4a-zL-UKQ" secondAttribute="trailing" id="gyj-E3-guF"/>
......
......@@ -43,7 +43,7 @@ class MaintaiDetailImageSmallCell: UICollectionViewCell {
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0)
make.size.equalTo(20)
make.size.equalTo(30)
}
}
......
......@@ -58,8 +58,8 @@ class MaintaiDetailTableViewCell: UITableViewCell {
func configChild(){
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: smallW, height: smallW)
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
layout.minimumLineSpacing = 12
layout.minimumInteritemSpacing = 12
layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
layout.scrollDirection = .horizontal
......@@ -135,8 +135,9 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
Print("主图滑动距离",scrollView.contentOffset.x)
let index = Int(scrollView.contentOffset.x/bigW)
Print("小图滑动到下标\(index)")
let space:CGFloat = (self.smallW + 12) * CGFloat(index)
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{
Print("小图滑动距离",offsetX)
let index = Int((offsetX/(smallW+12)).rounded()) //Int(offsetX/(smallW+12).rounded()) //rounded(offsetX/smallW)
Print("大图滑动到下标\(index)")
let space:CGFloat = (self.smallW + 12) * CGFloat(index)
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)
draggView = .null
......
......@@ -37,23 +37,20 @@ class MaintaiDetialVideoCell: UITableViewCell {
contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.equalTo(-15)
make.bottom.equalTo(-10)
make.size.equalTo(30)
make.right.equalTo(-22)
make.bottom.equalTo(0)
make.size.equalTo(34)
}
}
private func setupPlayer() {
// 创建播放器层
// playerLayer = AVPlayerLayer()
// playerLayer?.videoGravity = .resizeAspect
contentView.layer.addSublayer(playerLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
// 设置播放器层的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.masksToBounds = true
}
......@@ -92,53 +89,34 @@ class MaintaiDetialVideoCell: UITableViewCell {
return
}
if videoPlayer.rate != 0{
return
}
let item = AVPlayerItem.init(url: videoURL)
videoPlayer.replaceCurrentItem(with: item)
videoPlayer.play()
// // 创建播放器项
// let playerItem = AVPlayerItem(url: videoURL)
//
// // 创建播放器
// player = AVPlayer(playerItem: playerItem)
// playerLayer?.player = player
//
// // 设置静音
// player?.volume = 0
//
// // 播放视频
// player?.play()
//
// 添加播放完成的观察者
NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishPlaying),
name: .AVPlayerItemDidPlayToEndTime,
object: item)
}
//
@objc private func playerDidFinishPlaying() {
// // 播放结束后不做任何操作,因为只需要播放一次
// player?.pause()
// player?.seek(to: .zero)
//playerLayer.player?.seek(to: .zero)
videoPlayer.seek(to: .zero)
videoPlayer.play()
}
//
// override func prepareForReuse() {
// super.prepareForReuse()
// // 清理播放器
// player?.pause()
// player = nil
// NotificationCenter.default.removeObserver(self)
// }
//
@objc func selectChange(){
selectChangeBlock?()
}
deinit{
NotificationCenter.default.removeObserver(self)
}
}
......@@ -24,6 +24,7 @@ class NewGuideViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.transitioningDelegate = self
configUI()
startTimer()
}
......@@ -81,24 +82,27 @@ class NewGuideViewController: UIViewController {
}
func enterHome(){
let vc:HomeViewController = HomeViewController()
let nav = BaseNavViewController(rootViewController: vc)
// let vc:HomeViewController = HomeViewController()
//
// 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.saveUserDefToSandBox()
NotificationCenter.default.post(name: .guidePageClose, object: nil)
}
deinit{
......@@ -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 {
override func viewDidLoad() {
super.viewDidLoad()
// backView.addSubview(LaunchingView)
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)
// }
view.addSubview(LaunchingLoop)
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: {
var vc:UIViewController?
if (UserDef.shard.isShowLanding) {
vc = HomeViewController()
}else {
let Ssoryboard = UIStoryboard(name: "PermissionVC", bundle: nil)
if let current = Ssoryboard.instantiateViewController(identifier: "PermissionVCID") as? PermissionVC {
vc = current
}
}
guard let vc else {return}
let nav = BaseNavViewController(rootViewController: vc)
cWindow?.rootViewController = nav
let transition = CATransition()
transition.duration = 0.5
transition.subtype = CATransitionSubtype.fromRight // 从左侧推入
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
cWindow?.layer.add(transition, forKey: kCATransition)
cWindow?.makeKeyAndVisible()
self.dismiss(animated: false)
// var vc:UIViewController?
// if (UserDef.shard.isShowLanding) {
// vc = HomeViewController()
// }else {
// let Ssoryboard = UIStoryboard(name: "PermissionVC", bundle: nil)
// if let current = Ssoryboard.instantiateViewController(identifier: "PermissionVCID") as? PermissionVC {
// vc = current
// }
// }
// guard let vc else {return}
// let nav = BaseNavViewController(rootViewController: vc)
// cWindow?.rootViewController = nav
// let transition = CATransition()
// transition.duration = 0.5
// transition.subtype = CATransitionSubtype.fromRight // 从左侧推入
// transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
// 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 {
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{
// 免费试用订阅
dueDay.text = "Due today"
......
......@@ -102,10 +102,6 @@ class HomePayView:UIView {
let animationView = LottieAnimationView(name: "iapAnimation")
animationView.loopMode = .loop
// animationView.animationSpeed = -1.0
// animationView.layer.cornerRadius = 8
// animationView.contentMode = .scaleAspectFill
// animationView.clipsToBounds = true
return animationView
}()
......@@ -115,6 +111,7 @@ class HomePayView:UIView {
super.init(frame: frame)
setupUI()
setPayAnime()
}
required init?(coder: NSCoder) {
......@@ -161,64 +158,7 @@ class HomePayView:UIView {
}
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)
playAnimationWithDelay()
......@@ -228,27 +168,6 @@ class HomePayView:UIView {
make.width.equalTo(285.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?.text = " Secured with Apple"
......@@ -262,6 +181,7 @@ class HomePayView:UIView {
payButton?.setTitleColor(UIColor.colorWithHex(hexStr: whiteColor), for: .normal)
payButton?.titleLabel?.font = UIFont.scaledSystemFont(ofSize: 16, weight: .bold)
payButton?.backgroundColor = UIColor.colorWithHex(hexStr: mColor)
payButton?.isEnabled = false
payButton?.addTarget(self, action: #selector(payButtonClick), for: .touchUpInside)
self.addSubview(payButton!)
......@@ -340,49 +260,6 @@ class HomePayView:UIView {
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 {
self.weekProduct = week
self.lifeProduct = life
if week != nil,life != nil{
payButton?.isEnabled = true
}else{
payButton?.isEnabled = false
}
payDueView.reloadUI(type, week: week, life: life)
trailTitle.attributedText = nil
......@@ -537,7 +421,7 @@ class HomePayView:UIView {
}
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.font = UIFont.scaledSystemFont(ofSize: 12, weight: .regular)
tipsView.addSubview(trailTitle)
......@@ -552,46 +436,7 @@ class HomePayView:UIView {
make.left.right.equalToSuperview().inset(12)
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
make.left.right.equalToSuperview()
make.top.equalTo(tipsView.snp.bottom).offset(90.RH())
......@@ -631,4 +476,17 @@ class HomePayView:UIView {
addSubview(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
import SVProgressHUD
class HomePayViewController:UIViewController {
private var homePayView:HomePayView?
......@@ -121,7 +122,7 @@ extension HomePayViewController {
IAPManager.share.fetchProducts { [weak self] products in
guard let weakSelf = self else { return }
DispatchQueue.main.async {
if let (weekProduct, lifetimeProduct) = products {
if let (weekProduct, lifetimeProduct,_) = products {
weakSelf.homePayView?.reloadSKPorduct(week: weekProduct, life: lifetimeProduct)
}
}
......@@ -129,7 +130,7 @@ extension HomePayViewController {
}
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 }
switch result {
case .success(let success):
......@@ -182,4 +183,5 @@ extension HomePayViewController {
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 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<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"/>
<subviews>
<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>
<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"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</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">
<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>
<constraint firstAttribute="width" constant="325" id="7Q3-fW-f7G"/>
</constraints>
......@@ -33,7 +33,7 @@
<nil key="highlightedColor"/>
</label>
<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"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="2ml-bk-VZ7"/>
......@@ -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="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="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="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="Z86-W8-437" firstAttribute="trailing" secondItem="ci8-h6-fiE" secondAttribute="trailing" constant="5" id="wMs-cZ-eGL"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="295.41984732824426" y="-329.22535211267609"/>
<point key="canvasLocation" x="239.69465648854961" y="-368.66197183098592"/>
</view>
</objects>
<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{
return totalSize
}
}
......@@ -83,3 +83,11 @@ public func Alert( _ title:String? , _ message:String) -> Void {
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
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 {
//监听拿到基本相册资源
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