Commit c81e34c9 authored by CZ1004's avatar CZ1004

Merge branch 'charge' into develop

* charge:
  修改
  删除多余文件
  充电壁纸更新
  充电壁纸
  文件位置修改
  资源配置

# Conflicts:
#	PhoneManager.xcodeproj/project.pbxproj
parents 819a3325 8139f959
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>IntentsRestrictedWhileLocked</key>
<array/>
<key>IntentsRestrictedWhileProtectedDataUnavailable</key>
<array/>
<key>IntentsSupported</key>
<array>
<string>ChargeShowIntent</string>
<string>INSearchForMessagesIntent</string>
<string>INSendMessageIntent</string>
<string>INSetMessageAttributeIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.intents-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
</dict>
</dict>
</plist>
//
// IntentHandler.swift
// ChargeShow
//
// Created by edy on 2025/4/7.
//
import Intents
// As an example, this class is set up to handle Message intents.
// You will want to replace this or add other intents as appropriate.
// The intents you wish to handle must be declared in the extension's Info.plist.
// You can test your example integration by saying things to Siri like:
// "Send a message using <myApp>"
// "<myApp> John saying hello"
// "Search for messages in <myApp>"
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
print("有反应么")
return self
}
// MARK: - INSendMessageIntentHandling
// Implement resolution methods to provide additional information about your intent (optional).
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INSendMessageRecipientResolutionResult.needsValue()])
return
}
var resolutionResults = [INSendMessageRecipientResolutionResult]()
for recipient in recipients {
let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)]
case 1:
// We have exactly one matching contact
resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)]
case 0:
// We have no contacts matching the description provided
resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()]
default:
break
}
}
completion(resolutionResults)
} else {
completion([INSendMessageRecipientResolutionResult.needsValue()])
}
}
func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
if let text = intent.content, !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
} else {
completion(INStringResolutionResult.needsValue())
}
}
// Once resolution is completed, perform validation on the intent and provide confirmation (optional).
func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Verify user is authenticated and your app is ready to send a message.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
// Handle the completed intent (required).
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Implement your application logic to send a message here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
// Implement handlers for each intent you wish to handle. As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.
// MARK: - INSearchForMessagesIntentHandling
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
// Implement your application logic to find a message that matches the information in the intent.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
// Initialize with found message's attributes
response.messages = [INMessage(
identifier: "identifier",
content: "I am so excited about SiriKit!",
dateSent: Date(),
sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
)]
completion(response)
}
// MARK: - INSetMessageAttributeIntentHandling
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
// Implement your application logic to set the message attribute here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>INEnums</key>
<array/>
<key>INIntentDefinitionModelVersion</key>
<string>1.2</string>
<key>INIntentDefinitionNamespace</key>
<string>hAB6zR</string>
<key>INIntentDefinitionSystemVersion</key>
<string>24D81</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>16C5032a</string>
<key>INIntentDefinitionToolsVersion</key>
<string>16.2</string>
<key>INIntents</key>
<array>
<dict>
<key>INIntentCategory</key>
<string>generic</string>
<key>INIntentConfigurable</key>
<true/>
<key>INIntentDescription</key>
<string>充电壁纸</string>
<key>INIntentDescriptionID</key>
<string>wDHi0A</string>
<key>INIntentManagedParameterCombinations</key>
<dict>
<key></key>
<dict>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
<key>INIntentParameterCombinationUpdatesLinked</key>
<true/>
</dict>
</dict>
<key>INIntentName</key>
<string>ChargeShow</string>
<key>INIntentParameterCombinations</key>
<dict>
<key></key>
<dict>
<key>INIntentParameterCombinationIsPrimary</key>
<true/>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
</dict>
</dict>
<key>INIntentResponse</key>
<dict>
<key>INIntentResponseCodes</key>
<array>
<dict>
<key>INIntentResponseCodeName</key>
<string>success</string>
<key>INIntentResponseCodeSuccess</key>
<true/>
</dict>
<dict>
<key>INIntentResponseCodeName</key>
<string>failure</string>
</dict>
</array>
</dict>
<key>INIntentTitle</key>
<string>Charge Show</string>
<key>INIntentTitleID</key>
<string>wwbZJW</string>
<key>INIntentType</key>
<string>Custom</string>
<key>INIntentVerb</key>
<string>Do</string>
</dict>
</array>
<key>INTypes</key>
<array/>
</dict>
</plist>
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "logo.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
......
{
"images" : [
{
"filename" : "ic_close_charging.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ic_close_charging@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_close_charging@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
//
// ChargeGuideController.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
class ChargeGuideController : BaseViewController,UIScrollViewDelegate,UINavigationControllerDelegate {
lazy var guideScrollView: UIScrollView = {
let view = UIScrollView(frame: self.view.bounds)
view.contentSize = CGSize(width: self.view.bounds.width * CGFloat(8), height: self.view.bounds.height)
view.isPagingEnabled = true
view.showsHorizontalScrollIndicator = false
view.delegate = self
return view
}()
lazy var closeButton : UIButton = {
let view = UIButton()
view.backgroundColor = .clear
view.setImage(UIImage(named: "ic_close_charging"), for: .normal)
view.addTarget(self, action: #selector(closeCurrentPage), for: .touchUpInside)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.guideScrollView)
self.view.addSubview(self.closeButton)
navigationController?.delegate = self
self.closeButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.top.equalToSuperview().offset(statusBarHeight + 15)
make.width.height.equalTo(28)
}
// 先添加一个快捷指令的页面
let shortcutsView = ChargeGuideOpenShortcutsView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
shortcutsView.backgroundColor = .gray
self.guideScrollView.addSubview(shortcutsView)
shortcutsView.callback = {[weak self] text in
guard let self else {return}
if text as! String == "next" {
self.guideScrollView.contentOffset = CGPointMake(self.view.bounds.width, 0)
}
}
for i in 1..<7{
let view = ChargeGuideNormalView(frame: CGRect(x: self.view.bounds.width * CGFloat(i), y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
self.guideScrollView.addSubview(view)
view.orderFlag = i
view.callback = {[weak self] text in
guard let self else {return}
if text as! String == "next" {
self.guideScrollView.contentOffset = CGPointMake(self.guideScrollView.contentOffset.x + self.view.bounds.width, 0)
}
if text as! String == "back" {
self.guideScrollView.contentOffset = CGPointMake(self.guideScrollView.contentOffset.x - self.view.bounds.width, 0)
}
}
}
let endView = ChargeGuideEndView(frame: CGRect(x: self.view.bounds.width * 7, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
endView.callback = {[weak self] text in
guard let self else {return}
if text as! String == "over" {
self.guideScrollView.contentOffset = CGPointMake(0, 0)
}
}
self.guideScrollView.addSubview(endView)
}
@objc func closeCurrentPage(){
self.navigationController?.popViewController(animated: true)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .pop {
return CustomPopAnimator()
}
return nil
}
}
// 自定义转场动画类
class CustomPopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey:.from),
let toVC = transitionContext.viewController(forKey:.to) else {
return
}
let containerView = transitionContext.containerView
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
let screenBounds = UIScreen.main.bounds
let finalFrame = CGRect(x: 0, y: screenBounds.height * 2, width: screenBounds.width, height: screenBounds.height)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromVC.view.frame = finalFrame
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
//
// ChargeGuideController.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
class ChargeGuideStartController : BaseViewController {
var callback : callBack<Any> = {text in}
lazy var backView : UIImageView = {
let view = UIImageView()
return view
}()
lazy var startButton : UIButton = {
let view = UIButton()
view.setTitle("Get Started", for: .normal)
view.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
view.setTitleColor(.white, for: .normal)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(getStart), for: .touchUpInside)
return view
}()
lazy var closeButton : UIButton = {
let view = UIButton()
view.backgroundColor = .clear
view.setImage(UIImage(named: "ic_close_charging"), for: .normal)
view.addTarget(self, action: #selector(closeCurrentPage), for: .touchUpInside)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.backView)
self.view.addSubview(self.startButton)
self.view.addSubview(self.closeButton)
self.backView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
self.startButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-70)
make.height.equalTo(46)
}
self.closeButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.top.equalToSuperview().offset(statusBarHeight + 15)
make.width.height.equalTo(28)
}
}
@objc func closeCurrentPage(){
self.dismiss(animated: true)
}
@objc func getStart(){
callback("getStart")
self.dismiss(animated: false)
}
}
......@@ -14,10 +14,12 @@ class ChargeInfoViewController:BaseViewController {
case charge
}
var model:ChargeViewCollectionModel?
var model:ChargeDataModel?
var type:ChargeInfoType?
var flag : Int?
var isShowBack:Bool? {
didSet {
......@@ -36,21 +38,21 @@ class ChargeInfoViewController:BaseViewController {
lazy var backImageView:ChargeInfoBackView = {
let sview:ChargeInfoBackView = ChargeInfoBackView(frame: view.bounds, backImage: model?.CoverImage ?? "")
let sview:ChargeInfoBackView = ChargeInfoBackView(frame: view.bounds, model: self.model!)
return sview
}()
lazy var settingView:ChargeInfoSettingView = {
let sview:ChargeInfoSettingView = ChargeInfoSettingView(frame: CGRect(x: 0, y: 0, width: view.width, height: 78 + safeHeight))
sview.model = self.model
sview.flag = self.flag
sview.isHidden = type == .setting ? false : true
return sview
}()
init(model: ChargeViewCollectionModel?,type:ChargeInfoType?) {
init(model: ChargeDataModel?,type:ChargeInfoType?) {
self.type = type
self.model = model
super.init(nibName: nil, bundle: nil)
......@@ -62,9 +64,9 @@ class ChargeInfoViewController:BaseViewController {
}
override func viewDidLoad() {
super.viewDidLoad()
self.barHidden = true
titleView.model.title = ""
......
......@@ -18,6 +18,7 @@ class ChargeViewController:BaseViewController {
sview.height = 20
sview.x = view.width - sview.width - 15
sview.centerY = navCenterY
sview.addTarget(self, action: #selector(guideClick), for: .touchUpInside)
return sview
}()
......@@ -36,10 +37,16 @@ class ChargeViewController:BaseViewController {
guard let self else {return}
if let cModel = model as? ChargeViewCollectionModel {
if let cModel = model as? ChargeDataModel {
sview.callOrder = {[weak self] flag in
guard let self else {return}
let vc:ChargeInfoViewController = ChargeInfoViewController(model: cModel, type: ChargeInfoViewController.ChargeInfoType.setting)
vc.flag = flag as? Int
self.navigationController?.pushViewController(vc, animated: true)
}
let vc:ChargeInfoViewController = ChargeInfoViewController(model: cModel, type: ChargeInfoViewController.ChargeInfoType.setting)
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
......@@ -64,5 +71,20 @@ class ChargeViewController:BaseViewController {
self.barHidden = false
}
// 跳转充电引导
@objc func guideClick(){
let vc : ChargeGuideStartController = ChargeGuideStartController()
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true)
vc.callback = {[weak self] text in
guard let self else {return}
let vc : ChargeGuideController = ChargeGuideController()
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
//
// ChargeDataModel.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
struct ChargeDataModel {
var isFree:Bool
var coverImage:UIImage?
var url : URL
init(isFree: Bool, coverImage: UIImage, url: URL) {
self.isFree = isFree
self.coverImage = coverImage
self.url = url
}
func toDictionary() -> [String: Any] {
if let imageData = coverImage!.pngData() {
print("成功将图片转换为 PNG 格式的 Data,数据大小: \(imageData.count) 字节")
return [
"isFree": isFree,
"coverImage": imageData,
"url": url.absoluteString
]
} else {
print("转换失败")
}
return [:]
}
init?(dictionary: [String: Any]) {
guard let isFree = dictionary["isFree"] as? Bool,
let coverImageData = dictionary["coverImage"] as? Data,
let url = dictionary["url"] as? String else {
return nil
}
self.isFree = isFree
self.coverImage = UIImage(data: coverImageData)!
self.url = URL(string: url)!
}
}
......@@ -6,6 +6,7 @@
//
import Foundation
import AVFoundation
func loadChargeImtesSONFromBundle() -> [ChargeViewCollectionModel]? {
// 获取 JSON 文件路径
......@@ -28,6 +29,30 @@ func loadChargeImtesSONFromBundle() -> [ChargeViewCollectionModel]? {
}
}
func loadVideoItems() -> [ChargeDataModel]{
let mainBundle = Bundle.main
// 获取指定文件夹下所有 MP4 文件的 URL
var dataArray : [ChargeDataModel] = []
if let mp4URLs = mainBundle.urls(forResourcesWithExtension: "mp4", subdirectory: nil) {
for url in mp4URLs {
let asset = AVURLAsset(url: url)
// 获取视频第一帧图片
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTimeMake(value: 0, timescale: 1)
do {
let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
let image = UIImage(cgImage: cgImage)
let model = ChargeDataModel(isFree: true, coverImage: image, url: url)
dataArray.append(model)
} catch {
print("获取视频图片出错: \(error.localizedDescription)")
}
}
}
return dataArray
}
struct ChargeViewCollectionModel:Codable {
var isFree:Bool
......
//
// ChargeGuideEndView.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
class ChargeGuideEndView : UIView{
var callback :callBack<Any> = {text in}
lazy var backView : UIImageView = {
let view = UIImageView()
return view
}()
lazy var gotoAnimationButton : UIButton = {
let view = UIButton()
view.setTitle("Go to Animations", for: .normal)
view.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
view.setTitleColor(.white, for: .normal)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(gotoAnimation), for: .touchUpInside)
return view
}()
lazy var startOverButton : UIButton = {
let view = UIButton()
view.setTitle("Start Over", for: .normal)
view.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(startOver), for: .touchUpInside)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.backView)
self.addSubview(self.gotoAnimationButton)
self.addSubview(self.startOverButton)
self.backView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
self.gotoAnimationButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-150)
make.height.equalTo(46)
}
self.startOverButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-70)
make.height.equalTo(46)
}
}
@objc func gotoAnimation(){
self.responderViewController()?.navigationController?.popViewController(animated: true)
}
@objc func startOver(){
callback("over")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ChargeGuideNormalView.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
class ChargeGuideNormalView : UIView{
var orderFlag : Int = 1
var callback :callBack<Any> = {text in}
lazy var backView : UIImageView = {
let view = UIImageView()
return view
}()
lazy var backButton : UIButton = {
let view = UIButton()
view.setTitle("Back", for: .normal)
view.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
view.setTitleColor(.white, for: .normal)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(getBack), for: .touchUpInside)
return view
}()
lazy var nextButton : UIButton = {
let view = UIButton()
view.setTitle("Next", for: .normal)
view.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(getNext), for: .touchUpInside)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.backView)
self.addSubview(self.backButton)
self.addSubview(self.nextButton)
self.backView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
self.backView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
self.backButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.width.equalTo(self.snp.width).multipliedBy(0.5).offset(-20)
make.bottom.equalToSuperview().offset(-70)
make.height.equalTo(46)
}
self.nextButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.width.equalTo(self.snp.width).multipliedBy(0.5).offset(-20)
make.bottom.equalToSuperview().offset(-70)
make.height.equalTo(46)
}
}
@objc func getBack(){
callback("back")
}
@objc func getNext(){
callback("next")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ChargeGuideOpenShortcutsView.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
class ChargeGuideOpenShortcutsView : UIView {
var callback :callBack<Any> = {text in}
lazy var backView : UIImageView = {
let view = UIImageView()
return view
}()
lazy var openButton : UIButton = {
let view = UIButton()
view.setTitle("Open Shortcuts", for: .normal)
view.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
view.setTitleColor(.white, for: .normal)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(getOpen), for: .touchUpInside)
return view
}()
lazy var nextButton : UIButton = {
let view = UIButton()
view.setTitle("Next", for: .normal)
view.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
view.setTitleColor(.black, for: .normal)
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.addTarget(self, action: #selector(getNext), for: .touchUpInside)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.backView)
self.addSubview(self.openButton)
self.addSubview(self.nextButton)
self.backView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
self.openButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-150)
make.height.equalTo(46)
}
self.nextButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-70)
make.height.equalTo(46)
}
}
@objc func getOpen(){
// 创建快捷指令 APP 的 URL
if let shortcutsURL = URL(string: "shortcuts://") {
// 检查设备是否能打开该 URL
if UIApplication.shared.canOpenURL(shortcutsURL) {
// 打开快捷指令 APP
UIApplication.shared.open(shortcutsURL, options: [:], completionHandler: { (success) in
if success {
print("成功跳转至快捷指令 APP")
} else {
print("无法跳转至快捷指令 APP")
}
})
} else {
print("设备不支持打开快捷指令 APP")
}
}
}
@objc func getNext(){
callback("next")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
......@@ -7,21 +7,16 @@
import UIKit
import SnapKit
import AVKit
class ChargeInfoBackView:UIView {
var backImage:String?
var model : ChargeDataModel?
private var timer: Timer?
lazy var backImageView:UIImageView = {
let sview:UIImageView = UIImageView()
sview.contentMode = .scaleToFill
sview.clipsToBounds = true
sview.isUserInteractionEnabled = true
lazy var videoPlayView:ChargeVideoPlayingView = {
let sview:ChargeVideoPlayingView = ChargeVideoPlayingView(frame: self.bounds)
return sview
}()
......@@ -53,14 +48,16 @@ class ChargeInfoBackView:UIView {
return label
}()
init(frame: CGRect,backImage:String) {
self.backImage = backImage
init(frame: CGRect,model:ChargeDataModel) {
super.init(frame: frame)
self.model = model
setupUI()
self.videoPlayView.playVideo(with: self.model!.url)
setupTimeUpdates()
BatteryMonitorManager.shared.delegate = self
......@@ -73,10 +70,9 @@ class ChargeInfoBackView:UIView {
}
func setupUI() {
backImageView.image = UIImage(named: self.backImage ?? "")
self.addSubview(backImageView)
self.addSubview(videoPlayView)
self.addSubview(timeLabel)
......@@ -190,7 +186,7 @@ class ChargeInfoBackView:UIView {
super.layoutSubviews()
backImageView.snp.makeConstraints { make in
videoPlayView.snp.makeConstraints { make in
make.centerX.width.height.equalToSuperview()
}
......
......@@ -9,6 +9,10 @@ import UIKit
class ChargeInfoSettingView:UIView {
var model : ChargeDataModel?
var flag : Int?
lazy var settingBtn:UIButton = {
let sview:UIButton = UIButton()
......@@ -48,8 +52,19 @@ class ChargeInfoSettingView:UIView {
}
@objc func settingBtnClick() {
// 存储到userDefaults
UserDefaults.standard.set(String(flag!), forKey: "chargePicInfo")
let alert = UIAlertController(title: nil, message: "完成!", preferredStyle: .alert)
self.responderViewController()?.present(alert, animated: true, completion: nil)
// 2 秒后关闭弹窗
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
alert.dismiss(animated: true, completion: nil)
self.responderViewController()?.navigationController?.popViewController(animated: true)
}
}
}
//
// VideoPlayingView.swift
// PhoneManager
//
// Created by edy on 2025/4/7.
//
import Foundation
import UIKit
import AVKit
class ChargeVideoPlayingView: UIView {
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .black
}
func playVideo(with url: URL) {
// 对 URL 字符串进行编码处理
guard let encodedURLString = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
print("URL 编码失败")
return
}
// 将编码后的字符串转换为 URL 对象
guard URL(string: encodedURLString) != nil else {
print("无法创建编码后的 URL")
return
}
self.player = AVPlayer(url: url)
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer?.frame = self.bounds
self.layer.addSublayer(playerLayer!)
// 监听视频加载状态
player?.addObserver(self, forKeyPath: "status", options: [.new, .old], context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
player?.play()
}
@objc private func playerDidFinishPlaying() {
player?.seek(to: CMTime.zero)
player?.play()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status" {
if let status = player?.status {
switch status {
case .readyToPlay:
print("视频准备好播放")
case .failed:
if let error = player?.error {
print("视频播放失败: \(error.localizedDescription)")
}
case .unknown:
print("视频状态未知")
@unknown default:
break
}
}
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
}
......@@ -6,14 +6,17 @@
//
import UIKit
import AVFoundation
class ChargeView:UIView {
var callBack:callBack<Any> = {model in }
var callBack : callBack<Any> = {model in }
var callOrder : callBack<Any> = {flag in }
let footerID:String = "footerID"
lazy var models:[ChargeViewCollectionModel] = []
lazy var models:[ChargeDataModel] = []
lazy var collectionView:UICollectionView = {
......@@ -53,11 +56,13 @@ class ChargeView:UIView {
super.init(frame: frame)
models = loadChargeImtesSONFromBundle() ?? []
models = loadVideoItems()
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
......@@ -86,7 +91,7 @@ extension ChargeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UIC
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChargeViewCollectionCell.identifiers, for: indexPath) as! ChargeViewCollectionCell
cell.flag = indexPath.row
cell.model = models[indexPath.row]
return cell
......@@ -97,6 +102,9 @@ extension ChargeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UIC
let model = models[indexPath.row]
self.callBack(model)
self.callOrder(indexPath.row)
}
func collectionView(_ collectionView: UICollectionView,
......@@ -118,7 +126,7 @@ extension ChargeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UIC
ofKind: kind,
withReuseIdentifier: footerID,
for: indexPath
)
)
return footer
}
......
......@@ -12,6 +12,10 @@ class ChargeViewCollectionCell:UICollectionViewCell {
static let identifiers = "ChargeViewCollectionCellID"
var flag : Int?
var videoURL : URL?
lazy var backImageView:UIImageView = {
let sview:UIImageView = UIImageView()
......@@ -56,7 +60,7 @@ class ChargeViewCollectionCell:UICollectionViewCell {
self.contentView.addSubview(isFreeBtn)
}
var model:ChargeViewCollectionModel! {
var model:ChargeDataModel! {
didSet {
......@@ -64,9 +68,11 @@ class ChargeViewCollectionCell:UICollectionViewCell {
guard let self else {return}
self.backImageView.image = UIImage.init(named: model.CoverImage)
self.backImageView.image = model.coverImage
self.isFreeBtn.isHidden = model.isFree
self.videoURL = model.url
}
}
}
......
......@@ -7,6 +7,7 @@
import Foundation
import Photos
import Lottie
class CompressCompletedViewController : BaseViewController{
......@@ -18,12 +19,12 @@ class CompressCompletedViewController : BaseViewController{
var currentMediaType : Int = 0
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 12
imageView.backgroundColor = .gray
return imageView
lazy var animationView : LottieAnimationView = {
let animationView = LottieAnimationView(name: "CompressCompletedLight")
animationView.layer.cornerRadius = 12
animationView.backgroundColor = .gray
animationView.loopMode = .loop
return animationView
}()
......@@ -152,7 +153,7 @@ class CompressCompletedViewController : BaseViewController{
self.view.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
self.titleView.isHidden = true
self.view.addSubview(self.imageView)
self.view.addSubview(self.animationView)
self.view.addSubview(self.tipLabel)
self.view.addSubview(self.detailTiplabel)
self.view.addSubview(self.infoView)
......@@ -168,13 +169,13 @@ class CompressCompletedViewController : BaseViewController{
self.view.addSubview(self.completedButton)
self.imageView.snp.makeConstraints { make in
self.animationView.snp.makeConstraints { make in
make.top.equalTo(statusBarHeight + 60)
make.width.height.equalTo(230)
make.centerX.equalToSuperview()
}
self.tipLabel.snp.makeConstraints { make in
make.top.equalTo(self.imageView.snp.bottom).offset(24)
make.top.equalTo(self.animationView.snp.bottom).offset(24)
make.width.equalTo(295)
make.height.equalTo(28)
make.centerX.equalToSuperview()
......
......@@ -29,7 +29,8 @@ class CompressQualityController : BaseViewController{
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 12
imageView.backgroundColor = .clear
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
return imageView
}()
......
......@@ -40,20 +40,30 @@ class CompressSwitchView : UIView {
self.backgroundColor = .clear
self.addSubview(self.leftButton)
self.addSubview(self.rightButton)
// // 暂时屏蔽
// self.addSubview(self.rightButton)
// self.leftButton.snp.makeConstraints { make in
// make.left.equalToSuperview().offset(4)
// make.top.equalToSuperview().offset(4)
// make.bottom.equalToSuperview().offset(-4)
// make.width.equalTo(self.snp.width).multipliedBy(0.5).offset(-6)
// }
//
// self.rightButton.snp.makeConstraints { make in
// make.right.equalToSuperview().offset(-4)
// make.top.equalToSuperview().offset(4)
// make.bottom.equalToSuperview().offset(-4)
// make.left.equalTo(self.leftButton.snp.right).offset(4)
// }
self.leftButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(4)
make.top.equalToSuperview().offset(4)
make.bottom.equalToSuperview().offset(-4)
make.width.equalTo(self.snp.width).multipliedBy(0.5).offset(-6)
}
self.rightButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-4)
make.top.equalToSuperview().offset(4)
make.bottom.equalToSuperview().offset(-4)
make.left.equalTo(self.leftButton.snp.right).offset(4)
}
// 初始化的时候设置默认值
......
......@@ -133,7 +133,6 @@ class HomeInfoViewController:BaseViewController {
tablewView.changeALlValue(isSeleted: seletedAllBtn.isSelected)
}
}
}
......@@ -85,15 +85,18 @@ class HomeViewController:BaseViewController {
func setupData() {
// 详情数据
PhotoDataManager.manager.loadFromFileSystem(resultModel: {[weak self] model in
self?.homeView?.model = model
})
// 总数据 文件数量和文件大小
PhotoAndVideoMananger.mananger.fetchAllFile {[weak self] index, FileSize in
guard let self else {return}
// 相当于进度条
self.homeView?.model?.allFileNumber = index
self.homeView?.model?.allFileSize = FileSize
self.homeView?.setTitle()
......@@ -117,13 +120,21 @@ class HomeViewController:BaseViewController {
if !isShowPay {
// 获取主页数据
setupData()
isShowPay = true
if BatteryMonitorManager.shared.getBatteryIsCharging() {
let vc:ChargeInfoViewController = ChargeInfoViewController(model:loadChargeImtesSONFromBundle()?.first, type: ChargeInfoViewController.ChargeInfoType.charge)
// 从字典里面取 如果有
var tempModel : ChargeDataModel?
if UserDefaults.standard.object(forKey: "chargePicInfo") == nil {
tempModel = loadVideoItems().first
}else{
let flag : String = UserDefaults.standard.object(forKey: "chargePicInfo") as! String
tempModel = loadVideoItems()[Int(flag)!]
}
let vc:ChargeInfoViewController = ChargeInfoViewController(model:tempModel, type: ChargeInfoViewController.ChargeInfoType.charge)
self.navigationController?.pushViewController(vc, animated: false)
......
......@@ -72,7 +72,7 @@ class HomeView:UIView {
func reload(type:PhotsFileType) {
var indexPath:IndexPath!
var indexPath : IndexPath
if type == .duplicates {
......@@ -90,8 +90,6 @@ class HomeView:UIView {
guard let self else {return}
self.collectionView.reloadData()
}
}
......@@ -138,7 +136,7 @@ class HomeView:UIView {
func setData() {
// 加载数据
}
......@@ -238,6 +236,10 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
return cell
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeOtherCollectionCell.identifier, for: indexPath) as! HomeOtherCollectionCell
cell.mediaType = 0
if indexPath.row == 0 {
cell.mediaType = 1
}
cell.model = model?.otherModelArray[indexPath.row]
return cell
default:
......
import UIKit
import Photos
import AVFoundation
class HomeOtherCollectionCell: UICollectionViewCell {
var similarFlag : Bool = false
// 如果是图片,类型为0,视频为1
var mediaType : Int = 0
// MARK: - Properties
static let identifier = "HomeOtherCollectionCellID"
......@@ -92,14 +97,41 @@ class HomeOtherCollectionCell: UICollectionViewCell {
titleLabel.text = model.folderName
guard let asset = model.assets.first?.first else {return}
let image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: asset)
DispatchQueue.main.async {[weak self] in
if self.mediaType == 0 {
let image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: asset)
DispatchQueue.main.async {[weak self] in
guard let self else {return}
imageView.image = image
}
}else{
guard let self else {return}
// 定义请求选项来获取视频的第一帧
let options = PHImageRequestOptions()
// 获取当前版本的照片或视频
options.version = .current
// 尽可能快地提供结果
options.deliveryMode = .opportunistic
// 允许从iCloud请求
options.isNetworkAccessAllowed = true
// 异步请求
options.isSynchronous = false
// 从 PHAsset 获取 AVAsset
if let videoAsset = PhotoAndVideoMananger.mananger.getPHAsssetwithID(ids: [asset]){
// 使用requestImageForAsset方法请求视频的第一帧图片
PHImageManager.default().requestImage(for: videoAsset, targetSize: CGSize(width: 400, height: 400), contentMode: PHImageContentMode.aspectFit, options: options) { image, _ in
// 处理获取到的图片
if let thumbnailImage = image {
// 使用获取到的图片,例如显示在UIImageView上
DispatchQueue.main.async {
// 确保在主线程更新UI
self.imageView.image = thumbnailImage
}
} else {
print("无法获取图片")
}
}
}
imageView.image = image
}
}
......
......@@ -84,8 +84,19 @@ class BatteryMonitorManager {
func getBatteryIsCharging() -> Bool {
UIDevice.current.isBatteryMonitoringEnabled = true
return UIDevice.current.batteryState == .charging
switch UIDevice.current.batteryState {
case.unknown:
print("电池状态未知")
case.unplugged:
print("电池未充电")
case.charging:
print("电池正在充电")
case.full:
print("电池已充满")
@unknown default:
break
}
return UIDevice.current.batteryState == .charging || UIDevice.current.batteryState == .full
}
// MARK: - Private Methods
......
......@@ -146,10 +146,8 @@ class PhotoAndVideoMananger {
guard let self = self else { return }
// 计算总大小
self.calculateTotalSize(of: combinedArray,progress: { fileSiez, index in
propress(index,Double(fileSiez))
}, completion: { fileSize,index in
completion(Double(fileSize),index)
})
......@@ -168,7 +166,6 @@ class PhotoAndVideoMananger {
propress(index,Double(fileSiez))
}, completion: { fileSize,index in
completion(Double(fileSize),index)
})
......@@ -606,6 +603,15 @@ class PhotoAndVideoMananger {
return assetsArray.first
}
func getPHAsssetwithIDs(ids :[String]) -> [PHAsset]? {
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
let assetsArray = fetchResult.objects(at: IndexSet(0..<fetchResult.count))
return assetsArray
}
func findBestQualityImage(from identifiers: [String]) async -> [String] {
var bestIds: [String] = []
var maxFileSize: Int64 = 0
......@@ -648,6 +654,149 @@ class PhotoAndVideoMananger {
}
}
// MARK: - 新方法处理相似图片向下-------------------------------------------------------
func dealSimilarPhotos(assets:[PHAsset],threshold: Double , completionHandler:@escaping ([[String]])->Void){
// 重复照片
var index = assets.startIndex
var count = 0
var finalData :[[String]] = []
while index < assets.endIndex {
let nextIndex = assets.index(index, offsetBy: 5, limitedBy: assets.endIndex) ?? assets.endIndex
// 当前组
let chunk = Array(assets[index..<nextIndex])
index = nextIndex
dealSimilarDetailPhotos(assets: chunk as [PHAsset], threshold: threshold) { data in
count = count + chunk.count
finalData = finalData + data
if count == assets.count {
completionHandler(finalData)
}
}
}
}
// 和上面的方法一起处理,防止内存崩溃
private func dealSimilarDetailPhotos(assets:[PHAsset],threshold: Double , completionHandler:@escaping ([[String]])->Void){
var groupAssets :[[String]] = []
// 图片请求选项
let imageRequestOptions = PHImageRequestOptions()
imageRequestOptions.deliveryMode = .highQualityFormat
imageRequestOptions.isSynchronous = true
imageRequestOptions.resizeMode = .exact
// 开一个线程去处理
DispatchQueue.global().async {
// 需要监听是否完成
var count1 = 0
var count2 = 0
for asset in assets {
PHImageManager.default().requestImage(for: asset, targetSize: CGSizeMake(50, 50), contentMode: .aspectFit, options: imageRequestOptions) { image1, info in
var currentGroup : [String] = []
// 如果当前组为空,先加一个上去
currentGroup.append(asset.localIdentifier)
// 然后分成区域比较
var index = assets.startIndex
while index < assets.endIndex {
let nextIndex = assets.index(index, offsetBy: 20, limitedBy: assets.endIndex) ?? assets.endIndex
// 当前组
var chunk = Array(assets[index..<nextIndex])
index = nextIndex
// 比较image重复
for item in chunk {
// 获取到当前图片
PHImageManager.default().requestImage(for: item, targetSize: CGSizeMake(50, 50), contentMode: .aspectFit, options: imageRequestOptions) { image2, info in
let isSamilar : Bool = OpenCVWrapper.areImagesSimilar(image1, withImage2: image2, threshold: threshold)
// 如果相似或者相同
if isSamilar {
currentGroup.append(item.localIdentifier)
}
count2 = count2 + 1
}
if count2 == assets.count && currentGroup.count >= 2 {
groupAssets.append(currentGroup)
}
}
// 将trunk移除
chunk.removeAll(keepingCapacity: false)
}
count1 = count1 + 1
currentGroup.removeAll(keepingCapacity: false)
}
if count1 == assets.count {
completionHandler(self.removeDuplicates(from: groupAssets))
}
}
}
}
/// 去掉数据重复
/// - Parameter array: 当前数据
/// - Returns: 去重后的数据
func removeDuplicates(from array: [[String]]) -> [[String]] {
var set = Set<[String]>()
var result: [[String]] = []
for subArray in array {
if set.insert(subArray).inserted {
result.append(subArray)
}
}
return result
}
// MARK: - 新方法处理相似图片向上-------------------------------------------------------
// MARK: - 处理视频数据-------------------------------------------------------
func dealVideoData(completionHandler:@escaping ([[String]])->Void){
var data:[String] = []
for item in self.videoAssets {
data.append(item.localIdentifier)
}
var finalData : [[String]] = []
finalData.append(data)
completionHandler(finalData)
}
// MARK: - 处理视频数据-------------------------------------------------------
// MARK: - 处理截图数据-------------------------------------------------------
func dealScreenShotData(completionHandler:@escaping ([[String]])->Void){
var data:[String] = []
for item in self.screenShotAssets {
data.append(item.localIdentifier)
}
var finalData : [[String]] = []
finalData.append(data)
completionHandler(finalData)
}
// MARK: - 处理截图数据-------------------------------------------------------
// MARK: - 处理照片数据-------------------------------------------------------
func dealImageAssetData(completionHandler:@escaping ([[String]])->Void){
var data:[String] = []
for item in self.imageAssets {
data.append(item.localIdentifier)
}
var finalData : [[String]] = []
finalData.append(data)
completionHandler(finalData)
}
// MARK: - 处理截图数据-------------------------------------------------------
}
......
......@@ -11,6 +11,7 @@
</dict>
<key>NSUserActivityTypes</key>
<array>
<string>ChargeShowIntent</string>
<string>LaunchAppIntent</string>
</array>
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
......
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