Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Sign in / Register
Toggle navigation
P
PhoneManager
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Yang
PhoneManager
Commits
56bb9c9b
Commit
56bb9c9b
authored
May 06, 2025
by
CZ1004
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【优化】优化视频压缩代码,添加重复联系人UI
parent
28ae9c45
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
484 additions
and
145 deletions
+484
-145
img_bj_battery.png
...ssets/Charge/img_bj_battery 1.imageset/img_bj_battery.png
+0
-0
img_bj_battery@2x.png
...ts/Charge/img_bj_battery 1.imageset/img_bj_battery@2x.png
+0
-0
img_bj_battery@3x.png
...ts/Charge/img_bj_battery 1.imageset/img_bj_battery@3x.png
+0
-0
AdvManager.swift
...ager/Class/Session/Advertisement/Manager/AdvManager.swift
+31
-9
CompressViewModel.swift
.../Class/Session/Compress/ViewModel/CompressViewModel.swift
+44
-90
ContactAllViewController.swift
...Session/Contact/Controller/ContactAllViewController.swift
+16
-11
ContactBackupViewController.swift
...sion/Contact/Controller/ContactBackupViewController.swift
+14
-13
ContactIncompleteViewController.swift
.../Contact/Controller/ContactIncompleteViewController.swift
+14
-11
ContactViewController.swift
...ss/Session/Contact/Controller/ContactViewController.swift
+28
-5
ContactsDupViewController.swift
...ession/Contact/Controller/ContactsDupViewController.swift
+49
-0
ContactModuleModel.swift
...ager/Class/Session/Contact/Model/ContactModuleModel.swift
+4
-4
CustomContactDupTableViewCell.swift
...ion/Contact/View/Cell/CustomContactDupTableViewCell.swift
+117
-0
ContactDupNormalView.swift
...Class/Session/Contact/View/Dup/ContactDupNormalView.swift
+151
-0
ContactModuleView.swift
...ass/Session/Contact/View/MenuView/ContactModuleView.swift
+1
-1
HomeViewController.swift
...er/Class/Session/Home/Controller/HomeViewController.swift
+13
-0
TabbarImtesData.json
PhoneManager/Class/Session/Home/Model/TabbarImtesData.json
+1
-0
Info.plist
PhoneManager/Info.plist
+1
-1
No files found.
PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery.png
deleted
100644 → 0
View file @
28ae9c45
188 KB
PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery@2x.png
deleted
100644 → 0
View file @
28ae9c45
985 KB
PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery@3x.png
deleted
100644 → 0
View file @
28ae9c45
This diff is collapsed.
Click to expand it.
PhoneManager/Class/Session/Advertisement/Manager/AdvManager.swift
View file @
56bb9c9b
...
...
@@ -9,11 +9,22 @@ import Foundation
import
GoogleMobileAds
import
UserMessagingPlatform
enum
AdvertisementType
{
case
rewardedInterstitialType
case
interstitialType
}
class
AdvManager
:
NSObject
,
FullScreenContentDelegate
{
// fixme:上线前更改
// private static let REWARDED_INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/4276457203"
// private static let INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/5836950888"
// info.plist: ca-app-pub-3480207748580737~4236262472
private
static
let
REWARDED_INTERSTITIALAD_KEY
:
String
=
"ca-app-pub-3940256099942544/6978759866"
private
static
let
INTERSTITIALAD_KEY
:
String
=
"ca-app-pub-3940256099942544/4411468910"
private
static
let
REWARDED_INTERSTITIALAD_KEY
:
String
=
"ca-app-pub-3480207748580737/4276457203"
private
static
let
INTERSTITIALAD_KEY
:
String
=
"ca-app-pub-3480207748580737/5836950888"
static
let
shared
:
AdvManager
=
AdvManager
()
...
...
@@ -26,6 +37,8 @@ class AdvManager : NSObject,FullScreenContentDelegate {
// 插页广告
var
interstitial
:
InterstitialAd
?
var
currentAdvType
:
AdvertisementType
=
.
rewardedInterstitialType
var
currentTimes
:
Int
=
3
/// 默认每日免费删除次数
...
...
@@ -137,9 +150,11 @@ class AdvManager : NSObject,FullScreenContentDelegate {
/// - Parameter completed: 准备完成后回调
func
showInterstitialAd
(
vc
:
UIViewController
)
{
guard
let
ad
=
self
.
interstitial
else
{
self
.
currentAdvType
=
.
rewardedInterstitialType
self
.
showRewardedInterstitialAd
(
vc
:
vc
)
return
}
self
.
currentAdvType
=
.
interstitialType
ad
.
present
(
from
:
vc
)
}
...
...
@@ -156,13 +171,20 @@ class AdvManager : NSObject,FullScreenContentDelegate {
func
adDidDismissFullScreenContent
(
_
ad
:
FullScreenPresentingAd
)
{
print
(
"Ad did dismiss full screen content."
)
if
self
.
currentAdvType
==
.
interstitialType
{
// 如果播放的是插页广告,播放完成应该再缓存一份
self
.
interstitial
=
nil
Task
{
await
self
.
loadInterstitial
()
}
}
else
{
// 如果播放的是插页激励,则应该同时缓存两个(因为只有在插页广告没有的情况下才会到插页激励广告)
self
.
rewardedInterstitialAd
=
nil
// 广告结束之后缓存新的
Task
{
// 同时load两个广告内容
await
self
.
loadInterstitial
()
await
self
.
loadRewardedInterstitialAd
()
}
}
// 更新值
updateAdvStrategyValue
()
...
...
PhoneManager/Class/Session/Compress/ViewModel/CompressViewModel.swift
View file @
56bb9c9b
...
...
@@ -190,14 +190,22 @@ class CompressViewModel{
Print
(
"---------原始大小:
\(
originalSize
)
"
)
//
激进
压缩设置
// 压缩设置
let
targetBitrate
:
Float
if
originalBitrate
>
0
{
// 强制压缩到原始比特率的quality比例,最低不低于500kbps
targetBitrate
=
max
(
originalBitrate
*
quality
,
500_000
)
// 最低500kbps
if
(
originalSize
<=
100000
){
// 当大小已经没有100KB时,按照0.95去压缩
targetBitrate
=
min
(
originalBitrate
*
quality
,
originalBitrate
*
0.95
)
}
else
{
// 最低500kbps
targetBitrate
=
max
(
originalBitrate
*
quality
,
500_000
)
}
}
else
{
// 无法获取原始比特率时的默认值
targetBitrate
=
quality
*
1_000_000
// 1Mbps为基准
// 1Mbps为基准
targetBitrate
=
quality
*
1_000_000
}
// 创建输出URL
...
...
@@ -205,19 +213,24 @@ class CompressViewModel{
.
appendingPathComponent
(
UUID
()
.
uuidString
)
.
appendingPathExtension
(
"mp4"
)
//
激进
压缩参数
// 压缩参数
let
compressionProperties
:
[
String
:
Any
]
=
[
AVVideoAverageBitRateKey
:
targetBitrate
,
AVVideoMaxKeyFrameIntervalKey
:
120
,
// 关键帧间隔加大到4秒(30fps)
AVVideoProfileLevelKey
:
AVVideoProfileLevelH264Baseline30
,
// 使用基线配置
AVVideoAllowFrameReorderingKey
:
false
,
// 禁用B帧
AVVideoExpectedSourceFrameRateKey
:
15
// 降低帧率到15fps
// 关键帧间隔加大到4秒(30fps)
AVVideoMaxKeyFrameIntervalKey
:
120
,
// 使用基线配置
AVVideoProfileLevelKey
:
AVVideoProfileLevelH264Baseline30
,
// 禁用B帧
AVVideoAllowFrameReorderingKey
:
false
,
// 降低帧率到15fps
AVVideoExpectedSourceFrameRateKey
:
15
]
let
videoSettings
:
[
String
:
Any
]
=
[
AVVideoCodecKey
:
AVVideoCodecType
.
h264
,
AVVideoWidthKey
:
videoTrack
.
naturalSize
.
width
/
2
,
// 分辨率减半
AVVideoHeightKey
:
videoTrack
.
naturalSize
.
height
/
2
,
// 分辨率减半
AVVideoWidthKey
:
videoTrack
.
naturalSize
.
width
*
2
/
3
,
AVVideoHeightKey
:
videoTrack
.
naturalSize
.
height
*
2
/
3
,
AVVideoScalingModeKey
:
AVVideoScalingModeResizeAspect
,
AVVideoCompressionPropertiesKey
:
compressionProperties
]
...
...
@@ -225,9 +238,12 @@ class CompressViewModel{
// 音频设置也进行压缩
let
audioSettings
:
[
String
:
Any
]
=
[
AVFormatIDKey
:
kAudioFormatMPEG4AAC
,
AVNumberOfChannelsKey
:
1
,
// 单声道
AVSampleRateKey
:
22050
,
// 降低采样率
AVEncoderBitRateKey
:
64_000
// 64kbps音频比特率
// 单声道
AVNumberOfChannelsKey
:
1
,
// 降低采样率
AVSampleRateKey
:
22050
,
// 64kbps音频比特率
AVEncoderBitRateKey
:
64_000
]
// 创建导出会话
...
...
@@ -257,7 +273,7 @@ class CompressViewModel{
let
compressedSize
=
attributes
[
.
size
]
as?
NSNumber
{
if
compressedSize
.
doubleValue
>=
originalSize
{
// 如果压缩后
仍然
大于等于原始大小,使用更激进的设置重新压缩
// 如果压缩后大于等于原始大小,使用更激进的设置重新压缩
try
?
FileManager
.
default
.
removeItem
(
at
:
outputURL
)
self
.
recompressWithMoreAggressiveSettings
(
avAsset
:
avAsset
,
originalSize
:
originalSize
,
index
:
index
)
{
url
,
error
in
outputURLs
[
index
]
=
url
...
...
@@ -288,74 +304,6 @@ class CompressViewModel{
}
}
private
static
func
videoComposition
(
for
asset
:
AVAsset
,
quality
:
Float
)
->
AVVideoComposition
?
{
guard
let
videoTrack
=
asset
.
tracks
(
withMediaType
:
.
video
)
.
first
else
{
return
nil
}
// 1. 获取原始视频的比特率作为参考
let
originalBitrate
=
videoTrack
.
estimatedDataRate
let
targetBitrate
:
Float
// 2. 根据质量参数和原始比特率计算目标比特率
if
originalBitrate
>
0
{
// 确保压缩后的比特率不超过原始比特率的quality比例
// 例如quality=0.7表示压缩到原始比特率的70%
// 最多压缩到95%,避免质量损失太小
targetBitrate
=
min
(
originalBitrate
*
quality
,
originalBitrate
*
0.95
)
}
else
{
// 无法获取原始比特率时的默认值
targetBitrate
=
quality
*
5_000_000
// 5Mbps为基准
}
// 3. 设置更高效的关键帧间隔(从默认的10秒改为30秒)
let
videoCompressionProperties
:
[
String
:
Any
]
=
[
AVVideoAverageBitRateKey
:
targetBitrate
,
// 关键帧间隔(帧数),30fps时约为3秒
AVVideoMaxKeyFrameIntervalKey
:
90
,
AVVideoProfileLevelKey
:
AVVideoProfileLevelH264HighAutoLevel
,
// 禁用B帧可减小文件但可能降低质量
AVVideoAllowFrameReorderingKey
:
false
]
// 4. 保持原始分辨率
let
naturalSize
=
videoTrack
.
naturalSize
let
videoSettings
:
[
String
:
Any
]
=
[
AVVideoCodecKey
:
AVVideoCodecType
.
h264
,
AVVideoWidthKey
:
naturalSize
.
width
,
AVVideoHeightKey
:
naturalSize
.
height
,
AVVideoScalingModeKey
:
AVVideoScalingModeResizeAspect
,
AVVideoCompressionPropertiesKey
:
videoCompressionProperties
]
let
composition
=
AVMutableVideoComposition
()
composition
.
renderSize
=
naturalSize
composition
.
frameDuration
=
CMTime
(
value
:
1
,
timescale
:
30
)
let
instruction
=
AVMutableVideoCompositionInstruction
()
instruction
.
timeRange
=
CMTimeRange
(
start
:
.
zero
,
duration
:
asset
.
duration
)
let
layerInstruction
=
AVMutableVideoCompositionLayerInstruction
(
assetTrack
:
videoTrack
)
instruction
.
layerInstructions
=
[
layerInstruction
]
composition
.
instructions
=
[
instruction
]
return
composition
}
private
static
func
presetName
(
for
quality
:
Float
)
->
String
{
switch
quality
{
case
0.8
...
1.0
:
return
AVAssetExportPresetHighestQuality
case
0.6
..<
0.8
:
return
AVAssetExportPreset1280x720
case
0.4
..<
0.6
:
return
AVAssetExportPreset960x540
case
0.2
..<
0.4
:
return
AVAssetExportPreset640x480
default
:
return
AVAssetExportPresetLowQuality
}
}
...
...
@@ -473,17 +421,20 @@ class CompressViewModel{
// 更激进的设置
let
compressionProperties
:
[
String
:
Any
]
=
[
AVVideoAverageBitRateKey
:
250_000
,
// 固定250kbps
AVVideoMaxKeyFrameIntervalKey
:
240
,
// 关键帧间隔8秒
// 固定250kbps
AVVideoAverageBitRateKey
:
250_000
,
// 关键帧间隔8秒
AVVideoMaxKeyFrameIntervalKey
:
240
,
AVVideoProfileLevelKey
:
AVVideoProfileLevelH264Baseline30
,
AVVideoAllowFrameReorderingKey
:
false
,
AVVideoExpectedSourceFrameRateKey
:
10
// 帧率降到10fps
// 帧率降到10fps
AVVideoExpectedSourceFrameRateKey
:
10
]
let
videoSettings
:
[
String
:
Any
]
=
[
AVVideoCodecKey
:
AVVideoCodecType
.
h264
,
AVVideoWidthKey
:
videoTrack
.
naturalSize
.
width
/
4
,
// 分辨率降到1/4
AVVideoHeightKey
:
videoTrack
.
naturalSize
.
height
/
4
,
AVVideoWidthKey
:
videoTrack
.
naturalSize
.
width
/
2
,
AVVideoHeightKey
:
videoTrack
.
naturalSize
.
height
/
2
,
AVVideoScalingModeKey
:
AVVideoScalingModeResizeAspect
,
AVVideoCompressionPropertiesKey
:
compressionProperties
]
...
...
@@ -491,8 +442,10 @@ class CompressViewModel{
let
audioSettings
:
[
String
:
Any
]
=
[
AVFormatIDKey
:
kAudioFormatMPEG4AAC
,
AVNumberOfChannelsKey
:
1
,
AVSampleRateKey
:
16000
,
// 16kHz采样率
AVEncoderBitRateKey
:
32_000
// 32kbps音频比特率
// 16kHz采样率
AVSampleRateKey
:
16000
,
// 32kbps音频比特率
AVEncoderBitRateKey
:
32_000
]
guard
let
exportSession
=
AVAssetExportSession
(
asset
:
avAsset
,
presetName
:
AVAssetExportPresetLowQuality
)
else
{
...
...
@@ -541,7 +494,8 @@ class CompressViewModel{
let
audioMix
=
AVMutableAudioMix
()
let
audioInputParams
=
AVMutableAudioMixInputParameters
(
track
:
audioTrack
)
audioInputParams
.
audioTimePitchAlgorithm
=
.
timeDomain
// 保持音频处理简单
// 保持音频处理简单
audioInputParams
.
audioTimePitchAlgorithm
=
.
timeDomain
audioMix
.
inputParameters
=
[
audioInputParams
]
return
audioMix
...
...
PhoneManager/Class/Session/Contact/Controller/ContactAllViewController.swift
View file @
56bb9c9b
...
...
@@ -10,7 +10,7 @@ import Foundation
class
ContactAllViewController
:
BaseViewController
{
var
dataSourceModel
:
[
ContactModel
]
=
[]
var
dataSourceModel
:
[
ContactModel
]
?
lazy
var
navView
:
ContactNavView
=
{
let
view
=
ContactNavView
()
...
...
@@ -71,11 +71,12 @@ extension ContactAllViewController {
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
super
.
viewWillAppear
(
animated
)
if
self
.
dataSourceModel
.
count
>
0
{
if
let
data
=
self
.
dataSourceModel
{
if
data
.
count
>
0
{
self
.
setNormalPage
()
self
.
normalView
.
dataSourceModel
=
self
.
dataSourceModel
self
.
normalView
.
dataSourceModel
=
data
DispatchQueue
.
main
.
async
{
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
data
.
count
)
Contacts"
self
.
normalView
.
sortContacts
()
self
.
normalView
.
tableView
.
reloadData
()
self
.
normalView
.
setupCustomIndexView
()
...
...
@@ -84,5 +85,9 @@ extension ContactAllViewController {
}
else
{
self
.
setDefaultPage
()
}
}
else
{
self
.
setDefaultPage
()
}
}
}
PhoneManager/Class/Session/Contact/Controller/ContactBackupViewController.swift
View file @
56bb9c9b
...
...
@@ -73,9 +73,8 @@ class ContactBackupViewController : BaseViewController {
let
vm
=
BackupViewModel
()
// 备份之前先看看是否有可用的联系人
vm
.
backupAllContacts
(
self
.
dataSourceAllModel
!.
allContacts
)
{
finished
,
error
in
if
let
data
=
self
.
dataSourceAllModel
{
vm
.
backupAllContacts
(
data
.
allContacts
)
{
finished
,
error
in
if
let
error
=
error
{
Print
(
"添加失败,
\(
error
.
localizedDescription
)
"
)
}
...
...
@@ -90,6 +89,8 @@ class ContactBackupViewController : BaseViewController {
}
}
}
}
}
}
...
...
PhoneManager/Class/Session/Contact/Controller/ContactIncompleteViewController.swift
View file @
56bb9c9b
...
...
@@ -12,7 +12,7 @@ class ContactIncompleteViewController : BaseViewController {
private
var
widthConstraint
:
Constraint
?
var
dataSourceModel
:
[
ContactModel
]
=
[]
var
dataSourceModel
:
[
ContactModel
]
?
lazy
var
navView
:
ContactNavView
=
{
let
view
=
ContactNavView
()
...
...
@@ -101,18 +101,21 @@ class ContactIncompleteViewController : BaseViewController {
extension
ContactIncompleteViewController
{
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
super
.
viewWillAppear
(
animated
)
if
self
.
dataSourceModel
.
count
>
0
{
if
let
data
=
self
.
dataSourceModel
{
if
data
.
count
>
0
{
self
.
setNormalPage
()
self
.
normalView
.
dataSourceModel
=
self
.
dataSourceModel
self
.
normalView
.
dataSourceModel
=
data
DispatchQueue
.
main
.
async
{
self
.
normalView
.
sortContacts
()
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
data
.
count
)
Contacts"
self
.
normalView
.
tableView
.
reloadData
()
}
}
else
{
self
.
setDefaultPage
()
}
}
else
{
self
.
setDefaultPage
()
}
}
}
PhoneManager/Class/Session/Contact/Controller/ContactViewController.swift
View file @
56bb9c9b
...
...
@@ -112,18 +112,33 @@ extension ContactViewController{
// 创建数组
var
incompleteContacts
:
[
ContactModel
]
=
[]
var
allContacts
:
[
ContactModel
]
=
[]
var
duplicates
:
[[
ContactModel
]]
=
[]
var
contactsByName
:
[
String
:
[
ContactModel
]]
=
[:]
try
store
.
enumerateContacts
(
with
:
request
)
{
contact
,
stop
in
let
givenName
=
contact
.
givenName
let
familyName
=
contact
.
familyName
let
fullName
=
"
\(
familyName
)
\(
givenName
)
"
let
fullName
=
"
\(
familyName
)\(
givenName
)
"
let
phoneNumbers
=
contact
.
phoneNumbers
.
map
{
$0
.
value
.
stringValue
}
let
model
=
ContactModel
.
init
(
name
:
fullName
,
phoneNumber
:
phoneNumbers
,
identifier
:
contact
.
identifier
)
if
fullName
.
isEmpty
||
phoneNumbers
.
count
<=
0
{
incompleteContacts
.
append
(
model
)
}
allContacts
.
append
(
model
)
self
.
dataSourceModel
=
ContactModuleModel
.
init
(
duplicates
:
[],
incompleteContacts
:
incompleteContacts
,
backups
:
[],
allContacts
:
allContacts
)
if
!
fullName
.
isEmpty
{
if
contactsByName
[
fullName
]
==
nil
{
contactsByName
[
fullName
]
=
[
model
]
}
else
{
contactsByName
[
fullName
]?
.
append
(
model
)
}
}
duplicates
=
contactsByName
.
values
.
filter
{
$0
.
count
>
1
}
}
self
.
dataSourceModel
=
ContactModuleModel
.
init
(
duplicates
:
sortDupDataSource
(
orgData
:
duplicates
),
incompleteContacts
:
incompleteContacts
,
backups
:
[],
allContacts
:
allContacts
)
DispatchQueue
.
main
.
async
{
self
.
updateModuleData
()
self
.
moduleView
.
tableView
.
reloadData
()
...
...
@@ -148,13 +163,13 @@ extension ContactViewController{
self
.
moduleView
.
tableView
.
reloadData
()
}
}
// fixme:获取重复数据
}
// MARK: 辅助方法
/// 获取联系人权限
/// - Parameter completion: 回调
func
requestContactsPermission
(
completion
:
@escaping
(
Bool
)
->
Void
)
{
let
store
=
CNContactStore
()
switch
CNContactStore
.
authorizationStatus
(
for
:
.
contacts
)
{
...
...
@@ -180,4 +195,12 @@ extension ContactViewController{
}
}
/// 重复项排序-做一个反序操作,让最新添加的联系人显示在最前面
/// - Parameter orgData: 原始数据
/// - Returns: 排序后的数据
func
sortDupDataSource
(
orgData
:[[
ContactModel
]])
->
[[
ContactModel
]]{
return
orgData
.
map
{
$0
.
reversed
()
}
}
}
PhoneManager/Class/Session/Contact/Controller/ContactsDupViewController.swift
View file @
56bb9c9b
...
...
@@ -19,10 +19,15 @@ class ContactsDupViewController : BaseViewController {
let
view
=
ContactNoDupView
()
return
view
}()
lazy
var
normalView
:
ContactDupNormalView
=
{
let
view
=
ContactDupNormalView
()
return
view
}()
// 默认页面
func
setDefaultPage
(){
self
.
normalView
.
removeFromSuperview
()
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
...
...
@@ -32,6 +37,17 @@ class ContactsDupViewController : BaseViewController {
}
}
func
setNormalPage
(){
self
.
emptyView
.
removeFromSuperview
()
self
.
view
.
addSubview
(
self
.
normalView
)
self
.
normalView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
navView
.
snp
.
bottom
)
.
offset
(
0
)
make
.
left
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
}
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
...
...
@@ -49,3 +65,36 @@ class ContactsDupViewController : BaseViewController {
}
extension
ContactsDupViewController
{
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
super
.
viewWillAppear
(
animated
)
if
let
data
=
self
.
dataSourceModel
{
if
data
.
count
>
0
{
self
.
setNormalPage
()
self
.
normalView
.
dataSourceModel
=
data
DispatchQueue
.
main
.
async
{
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
self
.
getCountFromDataSource
(
data
:
data
)
)
Contacts"
self
.
normalView
.
tableView
.
reloadData
()
}
}
else
{
self
.
setDefaultPage
()
}
}
else
{
self
.
setDefaultPage
()
}
}
func
getCountFromDataSource
(
data
:[[
ContactModel
]])
->
Int
{
var
totalElementCount
=
0
for
subArray
in
data
{
totalElementCount
+=
subArray
.
count
}
return
totalElementCount
}
}
PhoneManager/Class/Session/Contact/Model/ContactModuleModel.swift
View file @
56bb9c9b
...
...
@@ -40,13 +40,13 @@ extension ContactModel {
struct
ContactModuleModel
{
var
duplicates
:
[[
ContactModel
]]
var
duplicates
:
[[
ContactModel
]]
=
[]
var
incompleteContacts
:
[
ContactModel
]
var
incompleteContacts
:
[
ContactModel
]
=
[]
var
backups
:
[
BackupInfoModel
]
var
backups
:
[
BackupInfoModel
]
=
[]
var
allContacts
:
[
ContactModel
]
var
allContacts
:
[
ContactModel
]
=
[]
init
(
duplicates
:
[[
ContactModel
]],
incompleteContacts
:
[
ContactModel
],
backups
:
[
BackupInfoModel
],
allContacts
:
[
ContactModel
])
{
self
.
duplicates
=
duplicates
...
...
PhoneManager/Class/Session/Contact/View/Cell/CustomContactDupTableViewCell.swift
0 → 100644
View file @
56bb9c9b
//
// CustomContactDupTableViewCell.swift
// PhoneManager
//
// Created by edy on 2025/5/6.
//
import
Foundation
class
CustomContactDupTableViewCell
:
UITableViewCell
{
var
model
:
ContactModel
?{
didSet
{
self
.
nameLabel
.
text
=
model
?
.
name
if
let
numbers
=
model
?
.
phoneNumber
{
self
.
numberLabel
.
text
=
getPhoneNumberString
(
photoNumbers
:
numbers
)
}
else
{
self
.
numberLabel
.
text
=
""
}
}
}
lazy
var
backView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
(
red
:
0.95
,
green
:
0.96
,
blue
:
0.99
,
alpha
:
1
)
view
.
clipsToBounds
=
true
view
.
layer
.
cornerRadius
=
12
return
view
}()
lazy
var
nameLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
bold
)
return
label
}()
lazy
var
numberLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
textColor
=
UIColor
(
red
:
0.4
,
green
:
0.4
,
blue
:
0.4
,
alpha
:
1
)
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
14
,
weight
:
.
regular
)
return
label
}()
lazy
var
selectButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
named
:
"ic_sel_com"
),
for
:
.
normal
)
button
.
setImage
(
UIImage
(
named
:
"ic_unsel_com_red"
),
for
:
.
selected
)
button
.
addTarget
(
self
,
action
:
#selector(
selectContact(_:)
)
,
for
:
.
touchUpInside
)
return
button
}()
override
init
(
style
:
UITableViewCell
.
CellStyle
,
reuseIdentifier
:
String
?)
{
super
.
init
(
style
:
style
,
reuseIdentifier
:
reuseIdentifier
)
self
.
selectionStyle
=
.
none
self
.
contentView
.
addSubview
(
self
.
backView
)
self
.
backView
.
addSubview
(
self
.
nameLabel
)
self
.
backView
.
addSubview
(
self
.
numberLabel
)
self
.
backView
.
addSubview
(
self
.
selectButton
)
self
.
backView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
top
.
equalToSuperview
()
make
.
height
.
equalTo
(
71
)
}
self
.
nameLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalToSuperview
()
.
offset
(
16
)
make
.
width
.
equalTo
(
210
)
make
.
height
.
equalTo
(
22
)
}
self
.
numberLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
nameLabel
.
snp
.
bottom
)
.
offset
(
0
)
make
.
width
.
equalTo
(
210
)
make
.
height
.
equalTo
(
20
)
}
self
.
selectButton
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
24
)
make
.
centerY
.
equalToSuperview
()
make
.
right
.
equalToSuperview
()
.
offset
(
-
16
*
RScreenW
())
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
extension
CustomContactDupTableViewCell
{
@objc
private
func
selectContact
(
_
sender
:
UIButton
)
{
sender
.
isSelected
=
!
sender
.
isSelected
// buttonSelectCallBack(model!,sender.isSelected)
}
func
getPhoneNumberString
(
photoNumbers
:
[
String
])
->
String
{
var
tempNumbers
=
photoNumbers
let
nonEmptyStrings
=
tempNumbers
.
filter
{
!
$0
.
isEmpty
}
if
nonEmptyStrings
.
count
<=
0
{
return
""
}
return
photoNumbers
.
joined
(
separator
:
" / "
)
}
}
PhoneManager/Class/Session/Contact/View/Dup/ContactDupNormalView.swift
0 → 100644
View file @
56bb9c9b
//
// ContactDupNormalView.swift
// PhoneManager
//
// Created by edy on 2025/5/6.
//
import
Foundation
class
ContactDupNormalView
:
UIView
{
var
dataSourceModel
:
[[
ContactModel
]]
=
[]
var
selectData
:
[
Int
:
[
ContactModel
]]
=
[:]
/// 选择的联系人
private
var
selectedContacts
:
[
ContactModel
]
=
[]
lazy
var
titleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"All contacts"
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
20
,
weight
:
.
bold
)
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
textAlignment
=
.
left
return
label
}()
lazy
var
subTitleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
14
,
weight
:
.
regular
)
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
textAlignment
=
.
left
return
label
}()
lazy
var
tableView
:
UITableView
=
{
let
tableView
=
UITableView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
0
,
height
:
12
),
style
:
UITableView
.
Style
.
grouped
)
tableView
.
dataSource
=
self
tableView
.
delegate
=
self
tableView
.
register
(
CustomContactDupTableViewCell
.
self
,
forCellReuseIdentifier
:
"CustomContactDupTableViewCell"
)
tableView
.
separatorStyle
=
.
none
tableView
.
backgroundColor
=
.
clear
tableView
.
showsVerticalScrollIndicator
=
false
if
#available(iOS 15.0, *)
{
tableView
.
sectionHeaderTopPadding
=
0
}
return
tableView
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
self
.
addSubview
(
self
.
titleLabel
)
self
.
addSubview
(
self
.
subTitleLabel
)
self
.
addSubview
(
self
.
tableView
)
self
.
titleLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
top
.
equalToSuperview
()
.
offset
(
14
*
RScreenH
())
make
.
width
.
equalTo
(
345
*
RScreenW
())
make
.
height
.
equalTo
(
32
)
}
self
.
subTitleLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
top
.
equalTo
(
self
.
titleLabel
.
snp
.
bottom
)
.
offset
(
2
*
RScreenH
())
make
.
width
.
equalTo
(
345
*
RScreenW
())
make
.
height
.
equalTo
(
20
)
}
self
.
tableView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
44
*
RScreenH
())
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
extension
ContactDupNormalView
:
UITableViewDelegate
,
UITableViewDataSource
{
func
numberOfSections
(
in
tableView
:
UITableView
)
->
Int
{
return
self
.
dataSourceModel
.
count
}
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
return
self
.
dataSourceModel
[
section
]
.
count
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactDupTableViewCell"
,
for
:
indexPath
)
as!
CustomContactDupTableViewCell
cell
.
model
=
self
.
dataSourceModel
[
indexPath
.
section
][
indexPath
.
row
]
return
cell
}
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
return
77
+
8
*
RScreenH
()
}
func
tableView
(
_
tableView
:
UITableView
,
viewForHeaderInSection
section
:
Int
)
->
UIView
?
{
let
view
=
UIView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
self
.
tableView
.
width
,
height
:
22
))
view
.
backgroundColor
=
.
clear
let
label
=
UILabel
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
200
,
height
:
22
))
label
.
text
=
"
\(
self
.
dataSourceModel
[
section
]
.
count
)
Duplicate Contacts"
label
.
textAlignment
=
.
left
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
medium
)
label
.
textColor
=
UIColor
(
red
:
0.4
,
green
:
0.4
,
blue
:
0.4
,
alpha
:
1
)
view
.
addSubview
(
label
)
let
selectLabel
=
UILabel
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
100
,
height
:
22
))
selectLabel
.
center
=
CGPointMake
(
self
.
tableView
.
width
-
50
,
label
.
centerY
)
selectLabel
.
textAlignment
=
.
right
selectLabel
.
textColor
=
UIColor
(
red
:
0
,
green
:
0.51
,
blue
:
1
,
alpha
:
1
)
selectLabel
.
text
=
"Select All"
let
tap
=
UITapGestureRecognizer
()
tap
.
addTarget
(
self
,
action
:
#selector(
cellSelectTap
)
)
selectLabel
.
isUserInteractionEnabled
=
true
selectLabel
.
addGestureRecognizer
(
tap
)
view
.
addSubview
(
selectLabel
)
return
view
}
func
tableView
(
_
tableView
:
UITableView
,
heightForHeaderInSection
section
:
Int
)
->
CGFloat
{
return
34
}
// MARK: 响应方法
@objc
func
cellSelectTap
(){
}
// MARK: 辅助方法
}
PhoneManager/Class/Session/Contact/View/MenuView/ContactModuleView.swift
View file @
56bb9c9b
...
...
@@ -123,7 +123,7 @@ extension ContactModuleView:UITableViewDataSource, UITableViewDelegate {
if
indexPath
.
section
==
1
{
// 跳转不完整联系人页面
let
vc
:
ContactIncompleteViewController
=
ContactIncompleteViewController
()
vc
.
dataSourceModel
=
self
.
dataSourceModel
!
.
incompleteContacts
vc
.
dataSourceModel
=
self
.
dataSourceModel
?
.
incompleteContacts
self
.
responderViewController
()?
.
navigationController
?
.
pushViewController
(
vc
,
animated
:
true
)
}
if
indexPath
.
section
==
2
{
...
...
PhoneManager/Class/Session/Home/Controller/HomeViewController.swift
View file @
56bb9c9b
...
...
@@ -53,6 +53,19 @@ class HomeViewController:BaseViewController {
}
}
case
2
:
DispatchQueue
.
main
.
async
{[
weak
self
]
in
guard
let
self
else
{
return
}
let
vc
:
ContactViewController
=
ContactViewController
()
self
.
navigationController
?
.
pushViewController
(
vc
,
animated
:
true
)
}
break
case
3
:
DispatchQueue
.
main
.
async
{[
weak
self
]
in
guard
let
self
else
{
return
}
}
break
case
4
:
DispatchQueue
.
main
.
async
{[
weak
self
]
in
guard
let
self
else
{
return
}
let
vc
:
CompressController
=
CompressController
()
...
...
PhoneManager/Class/Session/Home/Model/TabbarImtesData.json
View file @
56bb9c9b
...
...
@@ -14,6 +14,7 @@
"heightImage"
:
"tabbar_contacts_hight"
,
"text"
:
"Contacts"
,
},
{
"normalImage"
:
"ic_email_home_pre"
,
"heightImage"
:
"tabbar_email_hight"
,
"text"
:
"Email Cleaner"
,
...
...
PhoneManager/Info.plist
View file @
56bb9c9b
...
...
@@ -26,7 +26,7 @@
<
fa
ls
e
/
>
<
/
d
i
c
t
>
<
k
e
y
>
GADApplicationIdentifier
<
/k
e
y
>
<
string
>
ca-app-pub-3
480207748580737
~
4236262472
<
/string
>
<
string
>
ca-app-pub-3
940256099942544
~
1458002511
<
/string
>
<
k
e
y
>
SKAdNetworkItems
<
/k
e
y
>
<
a
rr
a
y
>
<
d
i
c
t
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment