Commit 8139f959 authored by CZ1004's avatar CZ1004

Merge branch 'develop' into charge

* develop:
  4-8-1
  4-8
  分享、评论和内购
  小组件

# Conflicts:
#	PhoneManager.xcodeproj/project.pbxproj
parents 112ce94a ce62ba6f
......@@ -25,7 +25,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let current = Ssoryboard.instantiateViewController(identifier: "LauchVCID") as? LauchVC {
window?.rootViewController = current
window?.makeKeyAndVisible()
}
let battery = WidgetPublicModel.battery()
......@@ -37,7 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private func setupDefault() {
HomePayModel.share.checkTrialStatus()
NetStatusManager.manager.startNet { status in
switch status {
......
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "ic_idsp_unlock.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ic_idsp_unlock@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_idsp_unlock@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "ic_naal_unlock.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ic_naal_unlock@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_naal_unlock@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "ic_sbst_unlock.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ic_sbst_unlock@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_sbst_unlock@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -218,68 +218,76 @@ class CompressQualityController : BaseViewController{
@objc func submitAction(){
let compressingView : CompressingView = CompressingView(frame: self.view.bounds)
compressingView.data = self.model
self.view.addSubview(compressingView)
// 开始压缩
let manager : CompressViewModel = CompressViewModel()
var currentQulity = 0.2
if currentQulityType == 1 {
currentQulity = 0.5
}
if currentQulityType == 2 {
currentQulity = 0.8
}
var comDataSource : [Data] = []
if self.currentMediaType == 0 {
// 表示压缩图片
manager.compress(assets: self.model!, compressionQuality: currentQulity) {progress in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { compressedDataArray, errorArray in
var compressAllSize = 0.0
for (index, data) in compressedDataArray.enumerated() {
if let error = errorArray[index] {
print("第 \(index + 1) 个文件压缩出错: \(error.localizedDescription)")
} else if let data = data {
print("第 \(index + 1) 个文件压缩完成,压缩后大小: \(data.count) 字节")
compressAllSize = compressAllSize + Double(data.count)
comDataSource.append(data)
} else {
print("第 \(index + 1) 个文件压缩失败")
let actionBlock = { [weak self] in
guard let self = self else { return }
let compressingView : CompressingView = CompressingView(frame: self.view.bounds)
compressingView.data = self.model
self.view.addSubview(compressingView)
// 开始压缩
let manager : CompressViewModel = CompressViewModel()
var currentQulity = 0.2
if currentQulityType == 1 {
currentQulity = 0.5
}
if currentQulityType == 2 {
currentQulity = 0.8
}
var comDataSource : [Data] = []
if self.currentMediaType == 0 {
// 表示压缩图片
manager.compress(assets: self.model!, compressionQuality: currentQulity) {progress in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { compressedDataArray, errorArray in
var compressAllSize = 0.0
for (index, data) in compressedDataArray.enumerated() {
if let error = errorArray[index] {
print("第 \(index + 1) 个文件压缩出错: \(error.localizedDescription)")
} else if let data = data {
print("第 \(index + 1) 个文件压缩完成,压缩后大小: \(data.count) 字节")
compressAllSize = compressAllSize + Double(data.count)
comDataSource.append(data)
} else {
print("第 \(index + 1) 个文件压缩失败")
}
}
self.updateNextView(compressAllSize,compressingView,comDataSource,[])
}
self.updateNextView(compressAllSize,compressingView,comDataSource,[])
}
}else{
// 压缩视频
var compressAllSize : Double = 0.0
manager.compressVideos(models: self.model!, quality: Float(currentQulity)) { (identifier, progress) in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { (outputURLs, errors) in
for (index, outputURL) in outputURLs.enumerated() {
if let outputURL = outputURL {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
if let fileSize = attributes[.size] as? Int64 {
compressAllSize = compressAllSize + Double(fileSize)
}else{
// 压缩视频
var compressAllSize : Double = 0.0
manager.compressVideos(models: self.model!, quality: Float(currentQulity)) { (identifier, progress) in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { (outputURLs, errors) in
for (index, outputURL) in outputURLs.enumerated() {
if let outputURL = outputURL {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
if let fileSize = attributes[.size] as? Int64 {
compressAllSize = compressAllSize + Double(fileSize)
}
} catch {
Print("获取视频文件大小失败")
}
} catch {
Print("获取视频文件大小失败")
print("Compressed video \(index) saved at: \(outputURL)")
} else if let error = errors[index] {
print("Error compressing video \(index): \(error.localizedDescription)")
}
print("Compressed video \(index) saved at: \(outputURL)")
} else if let error = errors[index] {
print("Error compressing video \(index): \(error.localizedDescription)")
}
self.updateNextView(compressAllSize,compressingView,[],outputURLs)
}
self.updateNextView(compressAllSize,compressingView,[],outputURLs)
}
}
}
if HomePayModel.share.isNoAd == false {
HomeNoAdsViewController.show {
actionBlock()
}
}
}
}
......
......@@ -58,10 +58,9 @@ class CompressNavView : UIView {
}
@objc private func proBtnClick() {
let homeNavViewModel = HomeNavViewModel()
let vc = HomePayViewController()
vc.modalPresentationStyle = .fullScreen
homeNavViewModel.presentToDetailController(currentView: self, destnationController: vc)
HomePayViewController.show {
}
}
......
......@@ -56,16 +56,33 @@ class HomeInfoViewController:BaseViewController {
}
}
sview.deleteCallBack = {array in
sview.deleteCallBack = { [weak self] array in
guard let self = self else { return }
if let cA = array as? [String] {
PhotoAndVideoMananger.deleteAssets(localIdentifiers: cA) {[weak self] in
guard let self else {return}
self.tablewView.deleteModel()
let deleteOp:((Any)->Void) = {[weak self] imgs in
if let cA = imgs as? [String] {
PhotoAndVideoMananger.deleteAssets(localIdentifiers: cA) {[weak self] in
guard let self else {return}
self.tablewView.deleteModel()
}
}
}
if HomePayModel.share.isNoAd == false {
if self.type == .duplicates { // 重复
HomePayViewController.show {
deleteOp(array)
}
}else if self.type == .similar { // 相似
HomeNoAdsViewController.show {
deleteOp(array)
}
}else{
HomeNoAdsViewController.show {
deleteOp(array)
}
}
}else {
deleteOp(array)
}
}
......
......@@ -139,14 +139,9 @@ class HomeViewController:BaseViewController {
self.navigationController?.pushViewController(vc, animated: false)
}else {
let vc:HomePayViewController = HomePayViewController()
let nav:BaseNavViewController = BaseNavViewController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
self.navigationController?.present(nav, animated: true)
if HomePayModel.share.isNoAd == false {
HomePayViewController.show {}
}
}
}
}
......
......@@ -85,10 +85,9 @@ class HomeNavView:UIView {
}
@objc private func proBtnClick() {
let homeNavViewModel = HomeNavViewModel()
let vc = HomePayViewController()
vc.modalPresentationStyle = .fullScreen
homeNavViewModel.presentToDetailController(currentView: self, destnationController: vc)
HomePayViewController.show {
}
}
......
//
// HomeNoAdsViewController.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import UIKit
import StoreKit
class HomeNoAdsViewController: UIViewController, NoAdsStackDataSource {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
HomePayModel.share.storeCall = {[weak self] product in
guard let self = self else { return }
self.priceLabe?.text = "$\(product.price)/year, cancel anytime"
}
}
private var doneBlock:(()->Void) = {}
var priceLabe:UILabel?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
HomePayModel.share.fetchProducts()
setUI()
}
private lazy var stack:NoAdsStackView = {
let s = NoAdsStackView(3)
view.addSubview(s)
s.dataSource = self
return s
}()
private lazy var scroll: UIScrollView = {
let scroll = UIScrollView()
view.addSubview(scroll)
scroll.delegate = self;
scroll.showsHorizontalScrollIndicator = false
scroll.isPagingEnabled = true
scroll.clipsToBounds = true
return scroll
}()
private lazy var stack1:NoAdsStackView = {
let s = NoAdsStackView(descp.count)
scroll.addSubview(s)
s.axis = .horizontal
s.spacing = 0
s.distribution = .fillEqually
s.dataSource = self
return s
}()
private lazy var NoAdTitle: UILabel = {
let no = UILabel()
no.text = "Unlock all usage permissions"
no.font = UIFont.boldSystemFont(ofSize: 24)
no.textAlignment = .center
no.numberOfLines = 0
no.textColor = .black
view.addSubview(no)
return no
}()
private lazy var closeBtn: UIButton = {
let close = UIButton(type: .custom)
close.setImage(UIImage(named: "home_pay_close"), for: .normal)
view.addSubview(close)
close.addTarget(self, action: #selector(closeTouch), for: .touchUpInside)
return close
}()
private lazy var pageCtrol: PMPageControl = {
let page = PMPageControl()
page.currentPageIndicatorTintColor = .colorWithHex(hexStr: "#0082FF")
page.pageIndicatorTintColor = .colorWithHex(hexStr: "#C6CEE0")
page.numberOfPages = 3
view.addSubview(page)
return page
}()
private lazy var buybut: UIButton = {
let buy = UIButton(type: .custom)
buy.backgroundColor = UIColor.colorWithHex(hexStr: "#0082FF")
buy.setTitle("Start my 3-day free trial", for: .normal)
buy.layer.cornerRadius = 8
view.addSubview(buy)
buy.addTarget(self, action: #selector(paypuase), for: .touchUpInside)
return buy
}()
private lazy var bottomContentView: UIView = {
let content = UIView()
content.layer.cornerRadius = 12;
content.layer.borderColor = UIColor.colorWithHex(hexStr: "#0082FF").cgColor
content.layer.borderWidth = 1
view.addSubview(content)
let t = UILabel()
t.font = UIFont.systemFont(ofSize: 14)
t.textColor = .black
self.priceLabe = t;
content.addSubview(t)
let b = UILabel()
b.text = " 3-day FREE TRIAL"
b.font = UIFont.systemFont(ofSize: 12)
b.textColor = .colorWithHex(hexStr: "#666666")
content.addSubview(b)
t.snp.makeConstraints { make in
make.bottom.equalTo(content.snp.centerY).offset(-2)
make.left.equalToSuperview().offset(10)
}
b.snp.makeConstraints { make in
make.top.equalTo(content.snp.centerY)
make.left.equalToSuperview().offset(10)
}
return content
}()
private lazy var ppBtn:UIButton = {
let sview:UIButton = UIButton()
let content:String = "Terms"
let font = UIFont.systemFont(ofSize: 12.RW(), weight: .medium)
let color = UIColor.colorWithHex(hexStr: black6Color)
sview.setTitle(content, for: .normal)
sview.setTitleColor(color, for: .normal)
sview.titleLabel?.font = font
sview.sizeToFit()
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
let attributedString = NSAttributedString(string: content, attributes: attributes)
sview.setAttributedTitle(attributedString, for: .normal)
sview.addTarget(self, action: #selector(terms), for: .touchUpInside)
sview.sizeToFit()
view.addSubview(sview)
return sview
}()
private lazy var restoreBtn:UIButton = {
let sview:UIButton = UIButton()
let content:String = "Restore"
let font = UIFont.systemFont(ofSize: 12.RW(), weight: .medium)
let color = UIColor.colorWithHex(hexStr: black6Color)
sview.setTitle(content, for: .normal)
sview.setTitleColor(color, for: .normal)
sview.titleLabel?.font = font
sview.sizeToFit()
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
let attributedString = NSAttributedString(string: content, attributes: attributes)
sview.setAttributedTitle(attributedString, for: .normal)
sview.addTarget(self, action: #selector(restore), for: .touchUpInside)
sview.sizeToFit()
view.addSubview(sview)
return sview
}()
private let data:[[String:String]] = [
["icon":"ic_idsp_unlock","t":"Intelligent cleaning of similar photos"],
["icon":"ic_naal_unlock","t":"Unlimited usage times"],
["icon":"ic_sbst_unlock","t":"A more private space"],
]
private let descp:[String] = [
"That is great! Scan, remove similar photos, and compress videos. Simple and effective. Don't worry about the storage space decreasing",
"I have tried so many times to clean up storage on myphone but it always takes forever and never seems to beenough. This app has made it so much easier and faster",
"Thank goodness, all the storage space I use is slow.. I'm afraid of browsing and deleting unusable videos and photos, which makes things so simple"
]
}
extension HomeNoAdsViewController : UIScrollViewDelegate {
@objc func paypuase() -> Void {
HomePayModel.share.purchase()
}
@objc func restore() -> Void {
HomePayModel.share.restore()
}
@objc func terms() -> Void {
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:TermOfUseWebViewController = TermOfUseWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
func NoAdsStactChildView(_ content: NoAdsStackView, _ idx: Int) -> UIView {
if content == stack {
let stackview = UIStackView()
stackview.axis = .horizontal
stackview.spacing = 10
stackview.alignment = .center
stackview.distribution = .fillProportionally
let dict = data[idx]
let icon = UIImageView(image: UIImage(named: dict["icon"] ?? ""))
stackview.addArrangedSubview(icon)
icon.snp.makeConstraints { make in
make.height.equalTo(36)
make.width.equalTo(icon.snp.height)
}
let descp = UILabel()
descp.text = dict["t"]
descp.textColor = .black
descp.font = UIFont.boldSystemFont(ofSize: 16)
stackview.addArrangedSubview(descp)
return stackview
}else if content == stack1 {
let stackview = UIStackView()
stackview.axis = .vertical
stackview.spacing = 0
stackview.alignment = .center
stackview.distribution = .fillProportionally
let label = UILabel()
label.text = descp[idx]
label.textAlignment = .center
label.numberOfLines = 0
label.textColor = .colorWithHex(hexStr: "#333333")
label.font = UIFont.boldSystemFont(ofSize: 14)
stackview.addArrangedSubview(label)
return stackview
}else{
return UIView()
}
}
private func setUI() -> Void {
closeBtn.snp.makeConstraints { make in
make.left.top.equalTo(view).inset(UIEdgeInsets(top: 35, left: 13, bottom: 0, right: 0))
}
NoAdTitle.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(20)
make.top.equalTo(closeBtn.snp.bottom).offset(22)
}
stack.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(36)
make.top.equalTo(NoAdTitle.snp.bottom).offset(24)
}
scroll.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(30)
make.top.equalTo(stack.snp.bottom).offset(70)
make.height.equalTo(80)
}
stack1.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.height.equalTo(scroll.snp.height)
make.width.equalTo(scroll.snp.width).multipliedBy(3.0)
}
pageCtrol.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(stack1.snp.bottom).offset(15)
}
bottomContentView.snp.makeConstraints { make in
make.left.right.equalTo(buybut)
make.bottom.equalTo(buybut.snp.top).offset(-10)
make.height.equalTo(65)
}
buybut.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(15)
make.bottom.equalTo(ppBtn.snp.top).offset(-20)
make.height.equalTo(46)
}
ppBtn.snp.makeConstraints { make in
make.bottom.equalToSuperview().offset(-10)
make.left.equalToSuperview().offset(15)
}
restoreBtn.snp.makeConstraints { make in
make.bottom.equalToSuperview().offset(-10)
make.right.equalToSuperview().offset(-15)
}
DispatchQueue.main.async {
self.scroll.contentSize = CGSize(width: CGRectGetWidth(self.scroll.frame) * 3.0, height: 0)
self.bottomContentView.gradient(colors: [.colorWithHex(hexStr: "#FFFFFF") , .colorWithHex(hexStr: "#E3EDFC")])
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let width = scrollView.width
let offset = scrollView.contentOffset.x
let idx = ceil(offset / width)
self.pageCtrol.currentPage = Int(idx)
}
@objc private func closeTouch() -> Void {
self.dismiss(animated: true)
doneBlock()
}
class func show(_ compate:@escaping(()->Void) ) -> Void {
let vc = HomeNoAdsViewController()
vc.doneBlock = compate
let nav:BaseNavViewController = BaseNavViewController(rootViewController: vc)
nav.modalPresentationStyle = .overFullScreen
guard let root = UIViewController.topMostViewController() else { return }
root.present(nav, animated: true)
}
}
//
// NoAdsStackView.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import UIKit
protocol NoAdsStackDataSource: AnyObject {
func NoAdsStactChildView(_ content:NoAdsStackView, _ idx:Int) -> UIView
}
class NoAdsStackView: UIView {
var dataSource:(any NoAdsStackDataSource)?
var numbers:Int = 0 {
didSet{
self.loadUi()
}
}
private func loadUi() -> Void {
for i in 0..<self.numbers {
guard let child = dataSource?.NoAdsStactChildView(self , i) as? UIView else { return }
stack.addArrangedSubview(child)
}
}
var spacing:CGFloat = 0 {
didSet {
stack.spacing = spacing
}
}
var axis:NSLayoutConstraint.Axis = .vertical{
didSet{
stack.axis = axis
}
}
var alignment:UIStackView.Alignment = .fill {
didSet{
stack.alignment = alignment
}
}
var distribution:UIStackView.Distribution = .fill {
didSet{
stack.distribution = distribution
}
}
private lazy var stack: UIStackView = {
let stack = UIStackView()
stack.backgroundColor = .clear
stack.spacing = 8
stack.axis = .vertical
stack.alignment = .fill
stack.distribution = .equalSpacing
addSubview(stack)
return stack
}()
convenience init(_ numbers:Int) {
self.init(frame: CGRectZero)
self.numbers = numbers;
DispatchQueue.main.async {
self.loadUi()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
stack.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// HomePayModel.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import UIKit
import StoreKit
class HomePayModel: NSObject ,SKProductsRequestDelegate ,SKPaymentTransactionObserver {
static let share:HomePayModel = HomePayModel()
var isNoAd:Bool {
get {
guard let noabd = UserDefaults.standard.object(forKey: "noabd") as? Bool else { return false }
return noabd
}
set {
UserDefaults.standard.setValue((newValue), forKey: "noabd")
NotificationCenter.default.post(name: .HelperPurchaseNotification, object: nil)
UserDefaults.standard.synchronize()
}
}
var storeCall:((_ product:SKProduct) -> Void) = { prod in }
func checkTrialStatus() -> Void {
verifyReceiptWithApple { receipt in
self.alert.dismiss()
guard let json = receipt else {
self.refreshReceipt()
return
}
let sub = self.checkSubscriptionStatus(receiptInfo: json)
if sub.0 == true {
let extDate = sub.2
self.isNoAd = true
#if DEBUG
let s = DateFormatter()
s.dateFormat = "yyyyMMdd HHmmss"
Print("ext : %@", s.string(from: extDate ?? Date()))
#endif
}else{
self.isNoAd = false
}
}
}
func fetchProducts() {
let request = SKProductsRequest(productIdentifiers: ["com.app.phonemanager.year.member"])
request.delegate = self
request.start()
Print("获取商品信息")
}
var product:SKProduct?
/** 获取商品列表 */
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let products = response.products
guard let product = products.first else {
return
}
DispatchQueue.main.async {
self.product = product
self.storeCall(product)
}
}
/** 购买 */
func purchase() -> Void {
guard let prod = product,
SKPaymentQueue.canMakePayments()
else { return }
let payment = SKPayment(product: prod)
SKPaymentQueue.default().add(payment)
alert.show()
}
/** 恢复 */
func restore() -> Void {
SKPaymentQueue.default().restoreCompletedTransactions()
alert.show()
}
override init() {
super.init()
SKPaymentQueue.default().add(self)
}
let alert = PMAlertView()
}
extension HomePayModel {
/** 购买回调 */
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) -> Void {
guard let transaction = transactions.first else { return }
switch transaction.transactionState {
case .purchasing:
Print("购买中...")
break
case .purchased:
Print("购买成功")
self.handlePurchased(transaction)
break
case .failed:
Print("购买失败")
alert.dismiss()
SKPaymentQueue.default().finishTransaction(transaction)
break
case .restored:
Print("恢复购买")
self.restore()
break
default: break
}
}
/** 购买成功 */
private func handlePurchased(_ transaction: SKPaymentTransaction) {
checkTrialStatus()
SKPaymentQueue.default().finishTransaction(transaction)
}
func verifyReceiptWithApple(completion: @escaping (_ receipt:[String:Any]?) -> Void ) {
guard let receiptURL = Bundle.main.appStoreReceiptURL,
let receiptData = try? Data(contentsOf: receiptURL) else {
completion(nil)
return
}
let requestData: [String: Any] = [
"receipt-data": receiptData.base64EncodedString(),
"password": "3cbeb6f5ace84f5b98571263da74c192",
"exclude-old-transactions": true
]
// 2. 创建请求
let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")! // 生产环境
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: requestData)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let status = json["status"] as? Int, status == 21007 {
self.verifySandboxReceipt(receiptData: receiptData) { receipt in
completion(receipt)
}
} else {
completion(json)
}
} else {
completion(nil)
}
} catch {
completion(nil)
}
}
task.resume()
}
/** 验证收据 */
private func verifySandboxReceipt(receiptData: Data, completion: @escaping (_ receipt:[String:Any]?) -> Void) {
let base64Receipt = receiptData.base64EncodedString()
let requestData: [String: Any] = [
"receipt-data": base64Receipt,
"password": "3cbeb6f5ace84f5b98571263da74c192",
"exclude-old-transactions": true
]
let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")! // 沙盒环境
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: requestData)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
completion(json)
} else {
completion(nil)
}
} catch {
completion(nil)
}
}
task.resume()
}
/** 获取订阅状态 */
func checkSubscriptionStatus(receiptInfo: [String: Any]) -> (isActive: Bool, isTrial: Bool, expiresDate: Date?) {
guard let latestReceiptInfo = receiptInfo["latest_receipt_info"] as? [[String: Any]] else {
return (false, false, nil)
}
// 获取最新的订阅信息
guard let lastReceipt = latestReceiptInfo.first else {
return (false, false, nil)
}
// 解析试用期状态
let isTrial = (lastReceipt["is_trial_period"] as? String) == "true"
// 解析到期时间
var expiresDate: Date?
if let expiresDateMs = lastReceipt["expires_date_ms"] as? String,
let timeInterval = TimeInterval(expiresDateMs) {
expiresDate = Date(timeIntervalSince1970: timeInterval / 1000.0)
} else if let expiresDateString = lastReceipt["expires_date"] as? String {
let formatter = ISO8601DateFormatter()
expiresDate = formatter.date(from: expiresDateString)
}
// 检查订阅是否有效
let isActive: Bool
if let date = expiresDate {
isActive = date > Date()
} else {
isActive = false
}
return (isActive, isTrial, expiresDate)
}
/** 恢复购买 */
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) -> Void {
checkTrialStatus()
}
/** 刷新收据 */
func refreshReceipt() {
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}
}
extension Notification.Name {
static let HelperPurchaseNotification = Notification.Name("PhoneManagerisStore")
}
......@@ -8,6 +8,7 @@
import UIKit
import SnapKit
import Lottie
import StoreKit
class HomePayView:UIView {
......@@ -124,6 +125,7 @@ class HomePayView:UIView {
restoreBtn?.setTitle("Restore Purchase", for: .normal)
restoreBtn?.setTitleColor(UIColor.colorWithHex(hexStr: "#B3B3B3"), for: .normal)
restoreBtn?.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .regular)
restoreBtn?.addTarget(self, action: #selector(restoreTouch), for: .touchUpInside)
self.addSubview(restoreBtn!)
restoreBtn?.snp.makeConstraints { make in
......@@ -146,7 +148,7 @@ class HomePayView:UIView {
}
titleLabel1 = UILabel()
titleLabel1?.text = "Clean your Storage"
titleLabel1?.text = "Clean your storage space"
titleLabel1?.font = UIFont.systemFont(ofSize: 24, weight: .bold)
titleLabel1?.textColor = UIColor.colorWithHex(hexStr: black3Color)
self.addSubview(titleLabel1!)
......@@ -159,7 +161,7 @@ class HomePayView:UIView {
titleLabel1?.sizeToFit()
titleLabel2 = UILabel()
titleLabel2?.text = "Get rid of what you don't need"
titleLabel2?.text = "Delete unnecessary content"
titleLabel2?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
titleLabel2?.textColor = UIColor.colorWithHex(hexStr: black3Color)
self.addSubview(titleLabel2!)
......@@ -285,7 +287,7 @@ class HomePayView:UIView {
}
contentView1Tip1 = UILabel()
contentView1Tip1?.text = "Smart Cleaning, Video Compressor, Secret Storage, Manage Contacts, No Ads and Limits."
contentView1Tip1?.text = "Smart Cleaning, Video Compressor, Secret Storage, No Ads and Limits."
contentView1Tip1?.font = UIFont.systemFont(ofSize: 12, weight: .regular)
contentView1Tip1?.textColor = UIColor.colorWithHex(hexStr: black6Color)
contentView1Tip1?.numberOfLines = 2
......@@ -301,7 +303,6 @@ class HomePayView:UIView {
contentView1Tip1?.setlineSpacing(font: contentView1Tip1!.font, lineSpacing: 3, width: contentView1Tip1!.width, numberOfLines: contentView1Tip1!.numberOfLines, content: contentView1Tip1!.text ?? "")
contentView1Tip2 = UILabel()
contentView1Tip2?.text = "Free for 7 days, then $6.99/week"
contentView1Tip2?.font = UIFont.systemFont(ofSize: 12, weight: .regular)
contentView1Tip2?.textColor = UIColor.colorWithHex(hexStr: black6Color)
contentView1Tip2?.numberOfLines = 1
......@@ -328,7 +329,7 @@ class HomePayView:UIView {
contentView2?.layer.masksToBounds = true
contentView2Title = UILabel()
contentView2Title?.text = "Cleanup Pro"
contentView2Title?.text = "Free trial enabled"
contentView2Title?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
contentView2Title?.textColor = UIColor.colorWithHex(hexStr: black3Color)
self.contentView2?.addSubview(contentView2Title!)
......@@ -344,6 +345,8 @@ class HomePayView:UIView {
contentView2Switch?.onTintColor = UIColor.colorWithHex(hexStr: mColor)
contentView2Switch?.tintColor = .white
contentView2Switch?.backgroundColor = .clear
contentView2Switch?.isOn = true
contentView2Switch?.isUserInteractionEnabled = false
self.contentView2?.addSubview(contentView2Switch!)
contentView2Switch?.snp.makeConstraints { make in
......@@ -450,7 +453,7 @@ class HomePayView:UIView {
})
freeContentTip = UILabel()
freeContentTip?.text = "7 days free"
freeContentTip?.text = "3 days free"
freeContentTip?.font = UIFont.systemFont(ofSize: 12, weight: .bold)
freeContentTip?.textColor = UIColor.colorWithHex(hexStr: whiteColor)
freeContentTip?.backgroundColor = UIColor.colorWithHex(hexStr: "#52C776")
......@@ -478,10 +481,9 @@ class HomePayView:UIView {
make.width.equalToSuperview().offset(-45)
})
normalContentTitle = UILabel()
normalContentTitle?.text = "Due today"
let date:String = Date().operation(1)?.string("MMMM dd yyyy") ?? ""
normalContentTitle?.text = "Due \(date)"
normalContentTitle?.font = UIFont.systemFont(ofSize: 12, weight: .regular)
normalContentTitle?.textColor = UIColor.colorWithHex(hexStr: black6Color)
normalContent?.addSubview(normalContentTitle!)
......@@ -503,7 +505,6 @@ class HomePayView:UIView {
make.centerX.equalToSuperview()
make.right.equalToSuperview()
})
}
func playAnimationWithDelay() {
......@@ -518,9 +519,15 @@ class HomePayView:UIView {
}
}
var product : SKProduct? {
didSet{
guard let prod = product else { return }
normalContentMoney?.text = "$\(prod.price)"
contentView1Tip2?.text = "Free trial for 3 days, $\(prod.price) per year thereafter"
}
}
@objc func closeBtnClick() {
callBack(OperStatus.close)
}
......@@ -530,18 +537,19 @@ class HomePayView:UIView {
}
@objc func touBtnClick(btn:UIButton) {
callBack(CommonPush.tou)
}
@objc func switchValueChanged(_ sender: UISwitch) {
callBack(CommonPush.swit)
}
@objc func restoreTouch() -> Void {
callBack(CommonPush.restore)
}
@objc func payButtonClick() {
callBack(CommonPush.pay)
}
}
......@@ -6,33 +6,42 @@
//
import UIKit
import StoreKit
class HomePayViewController:UIViewController {
private var homePayView:HomePayView?
private var disjunctor = false
private var doneBlock:(()->Void) = {}
var currentProduct:SKProduct? {
didSet {
setPrice()
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
addViews()
HomePayModel.share.fetchProducts()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
storeKeD()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
homePayView?.playAnimationWithDelay()
}
private func addViews() {
homePayView = HomePayView(frame: view.bounds)
view.addSubview(homePayView!)
homePayView?.callBack = {[weak self] status in
guard let self else {return}
......@@ -42,6 +51,7 @@ class HomePayViewController:UIViewController {
switch operstatus {
case .close:
self.dismiss(animated: true)
doneBlock()
default:
break
}
......@@ -54,18 +64,49 @@ class HomePayViewController:UIViewController {
self.ppClick()
case .tou:
self.touClick()
case .pay:
self.payTouch()
break
case .swit:
self.disjunctorTouch()
break
case .restore:
self.restoreClick()
break
}
}
}
}
}
extension HomePayViewController {
private func setPrice() -> Void {
guard currentProduct != nil else { return }
homePayView?.product = currentProduct
}
private func storeKeD() -> Void {
HomePayModel.share.storeCall = {[weak self] product in
guard let self = self else { return }
self.currentProduct = product
}
}
private func disjunctorTouch() -> Void {
self.disjunctor = !self.disjunctor
setPrice()
}
private func payTouch() -> Void {
HomePayModel.share.purchase()
}
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)
......@@ -73,14 +114,25 @@ class HomePayViewController:UIViewController {
}
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)
}
}
private func restoreClick() {
HomePayModel.share.restore()
}
class func show( _ compate:@escaping(()->Void)) -> Void {
let vc:HomePayViewController = HomePayViewController()
vc.doneBlock = compate
let nav:BaseNavViewController = BaseNavViewController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
guard let rt = UIViewController.topMostViewController() else { return }
rt.present(nav, animated: true)
}
}
......@@ -112,10 +112,10 @@ class SettingViewHeaderCell : UITableViewCell {
}
@objc func leanMoreBtnClick() {
let homeNavViewModel = HomeNavViewModel()
let vc = HomePayViewController()
vc.modalPresentationStyle = .fullScreen
homeNavViewModel.presentToDetailController(currentView: self, destnationController: vc)
HomeNoAdsViewController.show {
}
}
}
......@@ -59,7 +59,6 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
let cell : SettingViewHeaderCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewHeaderCell",for: indexPath) as! SettingViewHeaderCell
return cell
}else{
let model : SettingModel = modelData![indexPath.section]
let detailModel : RowInfoModel = model.rowInfo[indexPath.row]
......@@ -145,8 +144,48 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let signOutTipView = SignOutTipView(frame: self.view.frame)
self.view.addSubview(signOutTipView)
switch indexPath.section {
case 0:
break
case 1:
break
case 2: // 小组件
let widget = WidgetViewController()
self.navigationController?.pushViewController(widget, animated: true)
break
case 3:
break
case 4:
if indexPath.row == 0 { // 评分
self.review()
}else if indexPath.row == 1 { // 分享
self.PhoneShare()
}
break
default:
break
}
}
private func review() -> Void {
let reviewURLString = "itms-apps://itunes.apple.com/app/id1530333201?action=write-review"
guard let url = URL(string: reviewURLString), UIApplication.shared.canOpenURL(url) else {
return
}
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
private func PhoneShare() -> Void {
let items: [Any] = [ URL(string: "itms-apps://itunes.apple.com/app/id1530333201")! ]
let shareVc = UIActivityViewController(
activityItems: items,
applicationActivities: nil
)
present(shareVc, animated: true)
}
}
//
// PMAlertView.swift
// PhoneManager
//
// Created by edy on 2025/4/8.
//
import UIKit
class PMAlertView: UIViewController {
private var pm_msg:String?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setUI()
}
private func setUI() -> Void {
pm_ActivityIndicator.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(13)
make.size.equalTo(CGSize(width: 24, height: 24))
}
massage.snp.makeConstraints { make in
make.top.equalTo(pm_ActivityIndicator.snp.bottom).offset(15)
make.width.lessThanOrEqualTo(140)
make.width.greaterThanOrEqualTo(50)
make.right.left.equalToSuperview()
make.bottom.equalTo(content.snp.bottom).offset(-5)
}
content.snp.makeConstraints { make in
make.centerX.centerY.equalToSuperview()
}
}
func show() -> Void {
DispatchQueue.main.async {
guard let rt = UIViewController.topMostViewController() else { return }
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
rt.present(self, animated: true)
}
}
func dismiss() -> Void {
DispatchQueue.main.async {
self.dismiss(animated: true)
}
}
convenience init( _ message:String?) {
self.init()
self.pm_msg = message
}
private lazy var pm_ActivityIndicator: UIActivityIndicatorView = {
let pmact = UIActivityIndicatorView()
pmact.style = .medium
pmact.startAnimating()
content.addSubview(pmact)
pmact.color = .black
pmact.transform = CGAffineTransformMakeScale(1.3, 1.3)
return pmact
}()
private lazy var content: UIView = {
let content = UIView()
content.backgroundColor = .clear
content.layer.cornerRadius = 14;
view.addSubview(content)
return content
}()
private lazy var massage: UILabel = {
let msg = UILabel()
msg.textAlignment = .center
msg.numberOfLines = 0
msg.font = UIFont.systemFont(ofSize: 15)
msg.textColor = .colorWithHex(hexStr: "#333333")
content.addSubview(msg)
return msg
}()
}
//
// PMPageControl.swift
// PhoneManager
//
// Created by edy on 2025/4/8.
//
import UIKit
class PMPageControl: UIPageControl {
private let activeWidth: CGFloat = 16.0
private let inactiveWidth: CGFloat = 8.0
private let dotSpacing: CGFloat = 8.0
override func layoutSubviews() {
super.layoutSubviews()
// for (index, dot) in subviews.enumerated() {
// let isCurrentPage = index == currentPage
// let targetWidth = isCurrentPage ? activeWidth : inactiveWidth
// let previousDotsWidth = CGFloat(index) * (inactiveWidth + dotSpacing)
// let adjustX = previousDotsWidth + (isCurrentPage ? 0 : activeWidth - inactiveWidth)
// dot.frame = CGRect(
// x: adjustX,
// y: dot.frame.origin.y,
// width: targetWidth,
// height: dot.frame.height
// )
// }
}
}
......@@ -22,6 +22,9 @@ enum CommonPush {
case pp
case tou
case swit
case pay
case restore
}
enum tabbarType {
......
......@@ -214,3 +214,33 @@ enum GradientDirection {
}
}
}
extension Date {
func string(_ format:String = "") -> String {
let dateforametter = DateFormatter()
dateforametter.dateFormat = format
return dateforametter.string(from: self)
}
func operation(_ year:Int = 0,
_ month:Int = 0,
_ day:Int = 0,
_ houre:Int = 0,
_ minte:Int = 0,
_ sec:Int = 0,
_ week:Int = 0) -> Date? {
let calendar = Calendar.current
let currentDate = self
var dateComponent:DateComponents = calendar.dateComponents([.year,.month,.day,.hour,.minute,.second,.weekday], from: currentDate)
dateComponent.year! += year
dateComponent.month! += month
dateComponent.month! += day
dateComponent.hour! += houre
dateComponent.second! += sec
dateComponent.weekday! += week
guard let toDate = calendar.date(from: dateComponent) else { return self }
return toDate
}
}
......@@ -23,8 +23,40 @@ extension UIViewController {
nav.barHidden = isHidden
}
class func topMostViewController() -> UIViewController? {
guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
return nil
}
return self.topMostViewController(of: rootViewController)
}
private static func topMostViewController(of viewController: UIViewController) -> UIViewController {
// 处理模态弹出的视图控制器
if let presentedViewController = viewController.presentedViewController {
return self.topMostViewController(of: presentedViewController)
}
// 处理 UINavigationController
if let navigationController = viewController as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
return self.topMostViewController(of: visibleViewController)
}
// 处理 UITabBarController
if let tabBarController = viewController as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return self.topMostViewController(of: selectedViewController)
}
// 处理子控制器
for subview in viewController.view?.subviews ?? [] {
if let childViewController = subview.next as? UIViewController {
return self.topMostViewController(of: childViewController)
}
}
return viewController
}
}
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