Commit 3349dd14 authored by shenyong's avatar shenyong

首页数据获取

parent 21848fa1
......@@ -46,6 +46,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// 初始化广告SDK
AdvManager.shared.initAdertisementSDK()
PhotoManager.shared.config()
return true
}
......@@ -79,22 +81,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
/// 首次进来开始请求首页数据
func findHomeData(){
PhotoAndVideoMananger.getPrivacy {[weak self] status in
guard let self else {return}
if let photoStatus = status as? PrivacyType {
Singleton.shared.photoPermission = photoStatus
if photoStatus == .authorized {
// 有授权加载数据
PhotoAndVideoMananger.mananger.setAssets()
// 读取缓存数据
readCacheModel()
}else{
// 没有授权,更新删除缓存数据
PhotoDataManager.manager.loadDataFromPhotos { model in}
Print("未获取授权")
}
}
}
// PhotoAndVideoMananger.getPrivacy {[weak self] status in
// guard let self else {return}
// if let photoStatus = status as? PrivacyType {
// Singleton.shared.photoPermission = photoStatus
// if photoStatus == .authorized {
// // 有授权加载数据
// PhotoAndVideoMananger.mananger.setAssets()
// // 读取缓存数据
// readCacheModel()
// }else{
// // 没有授权,更新删除缓存数据
// PhotoDataManager.manager.loadDataFromPhotos { model in}
// Print("未获取授权")
// }
// }
// }
}
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -133,7 +133,7 @@ extension IAPManager {
switch result {
case .success(let receipt):
// 打印完整收据信息,方便调试
print("收据信息:\(receipt)")
//print("收据信息:\(receipt)")
let status = self.checkSubscriptionStatus(receiptInfo: receipt)
DispatchQueue.main.async {
......
......@@ -79,7 +79,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
return
}
let maxConcurrency = 4 // 最大并发数
let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, resolutionGroups.count / maxConcurrency)
for batchIndex in stride(from: 0, to: resolutionGroups.count, by: batchSize) {
......
......@@ -108,7 +108,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable {
print("开始处理分组,分组资源为:",unprocessedGroups.count)
let maxConcurrency = 3 // 最大并发数
let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.count == 0{
......
......@@ -134,7 +134,7 @@ class VideoSimilarJSONManager: @unchecked Sendable {
}
// 6. 并发处理未处理的组
let maxConcurrency = 4 // 视频处理较重,降低并发数
let maxConcurrency = 3 // 视频处理较重,降低并发数
let batchSize = max(1, unprocessedGroups.count / maxConcurrency)
if unprocessedGroups.isEmpty {
......@@ -350,15 +350,25 @@ class VideoSimilarJSONManager: @unchecked Sendable {
let vectors = assetHashes.map { hashToVector($0.hash) }
// 3. 执行K-Means聚类
let k = min(vectors.count / 2, max(2, Int(sqrt(Double(vectors.count)))))
let clusters = kMeansClustering(vectors: vectors, k: k)
let k = min(vectors.count, 10) // 限制最多10个簇,与照片管理器保持一致
let labels = kMeansClustering(data: vectors, k: k)
// 4. 将聚类结果转换为相似组
var similarGroups: [[PHAsset]] = []
for cluster in clusters {
let groupAssets = cluster.indices.map { assetHashes[$0].asset }
if groupAssets.count > 1 {
similarGroups.append(groupAssets)
var groupedAssets: [Int: [PHAsset]] = [:]
// 根据标签将资源分组
for (index, label) in labels.enumerated() {
if groupedAssets[label] == nil {
groupedAssets[label] = []
}
groupedAssets[label]?.append(assetHashes[index].asset)
}
// 只保留包含多个资源的组
for (_, group) in groupedAssets {
if group.count > 1 {
similarGroups.append(group)
}
}
......@@ -384,56 +394,58 @@ class VideoSimilarJSONManager: @unchecked Sendable {
return vector
}
// K-Means聚类算法实现
private func kMeansClustering(vectors: [[Double]], k: Int) -> [[Int]] {
guard vectors.count >= k else { return [Array(0..<vectors.count)] }
// 1. 随机选择初始中心点
var centroids = (0..<k).map { _ in vectors[Int.random(in: 0..<vectors.count)] }
var clusters: [[Int]] = Array(repeating: [], count: k)
var previousClusters: [[Int]] = []
// K-Means 聚类算法
func kMeansClustering(data: [[Double]], k: Int, maxIterations: Int = 100) -> [Int] {
guard data.count > 0 && k > 0 && k <= data.count else {
return []
}
// 2. 迭代直到收敛或达到最大迭代次数
let maxIterations = 100
var iteration = 0
var centroids = (0..<k).map { _ in data.randomElement()! }
var labels = Array(repeating: 0, count: data.count)
while iteration < maxIterations {
// 清空当前聚类
clusters = Array(repeating: [], count: k)
for _ in 0..<maxIterations {
var newCentroids = Array(repeating: Array(repeating: 0.0, count: data[0].count), count: k)
var clusterCounts = Array(repeating: 0, count: k)
// 3. 分配点到最近的中心点
for (index, vector) in vectors.enumerated() {
// 分配数据点到最近的质心
for (i, point) in data.enumerated() {
var minDistance = Double.infinity
var closestCentroid = 0
for (centroidIndex, centroid) in centroids.enumerated() {
let distance = euclideanDistance(vector, centroid)
if distance < minDistance {
var closestCentroidIndex = 0
for (j, centroid) in centroids.enumerated() {
let distance = euclideanDistance(point, centroid)
if distance < minDistance && distance < 0.3 {
minDistance = distance
closestCentroid = centroidIndex
closestCentroidIndex = j
}
}
clusters[closestCentroid].append(index)
labels[i] = closestCentroidIndex
newCentroids[closestCentroidIndex] = newCentroids[closestCentroidIndex].enumerated().map { index, value in
value + point[index]
}
// 4. 检查是否收敛
if clusters == previousClusters {
break
clusterCounts[closestCentroidIndex] += 1
}
// 5. 更新中心点
// 更新质心
var hasChanged = false
for i in 0..<k {
guard !clusters[i].isEmpty else { continue }
let clusterVectors = clusters[i].map { vectors[$0] }
centroids[i] = calculateMean(clusterVectors)
if clusterCounts[i] > 0 {
let newCentroid = newCentroids[i].enumerated().map { index, value in
value / Double(clusterCounts[i])
}
if newCentroid != centroids[i] {
hasChanged = true
centroids[i] = newCentroid
}
}
}
previousClusters = clusters
iteration += 1
// 如果质心没有变化,提前结束迭代
if !hasChanged {
break
}
}
return clusters
return labels
}
// 计算欧氏距离
......
......@@ -117,6 +117,7 @@ class HomeInfoViewController:BaseViewController {
self?.tablewView.deleteModel(array: imgs)
}
})
self.setDefaultPage()
}
......
......@@ -10,7 +10,7 @@ import Photos
class HomeVideoDetailController :BaseViewController {
private var headerHeight : CGFloat = 98
private var headerHeight : CGFloat = 98 + 90
private var currentHeaderView: HomeVideoDetailCustomHeaderView?
......
//
// HomeUIModel.swift
// PhoneManager
//
// Created by edy on 2025/5/12.
//
import Foundation
struct HomeUIModel{
var allFileNumber:Int = 0
var allFileSize:Double = 0
var duplicatesPhotos:HomePhotosModel
var similarPhotos:HomePhotosModel
var videos:HomePhotosModel
var similarScreenShots:HomePhotosModel
var screenShots:HomePhotosModel
var similarVideos:HomePhotosModel
var otherPhotos:HomePhotosModel
}
class HomePhotosModel:Codable {
var folderName:String
var allFileSize:Double
var assets:[[AssetModel]]
init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) {
self.folderName = folderName
self.allFileSize = allFileSize
self.assets = assets
}
}
enum HomeUIEnum{
case Dublicates,Similar,Videos,SimilarScreenshots,Screensshots,SimilarVideos,Other
var title:String{
switch self {
case .Dublicates:
return "Dublicates"
case .Similar:
return "Similar"
case .Videos:
return "Videos"
case .SimilarScreenshots:
return "Similar Screenshots"
case .Screensshots:
return "Screensshots"
case .SimilarVideos:
return "Similar Videos"
case .Other:
return "Other"
}
}
var index:Int{
switch self {
case .Dublicates:
return 0
case .Similar:
return 1
case .Videos:
return 0
case .SimilarScreenshots:
return 1
case .Screensshots:
return 2
case .SimilarVideos:
return 3
case .Other:
return 4
}
}
}
......@@ -27,27 +27,28 @@ class HomeCollectionViewHeader : UICollectionReusableView {
let bar = CustomProgressBar()
return bar
}()
lazy var permissionView : PMPermissionView = {
let view = Bundle.main.loadNibNamed("PMPermissionView", owner: nil, options: nil)?.last as! PMPermissionView
return view
}()
// private lazy var tipLabel:UILabel = {
// let label = UILabel()
// label.numberOfLines = 0 // 支持多行
// return label
// }()
private lazy var tipLabel:UILabel = {
let label = UILabel()
label.numberOfLines = 0 // 支持多行
return label
}()
private func setupUI() {
// 文本
// self.addSubview(self.tipLabel)
// self.tipLabel.snp.makeConstraints { make in
// make.left.equalToSuperview().offset(8)
// make.top.equalToSuperview().offset(44)
// make.height.equalTo(17)
// }
self.addSubview(self.tipLabel)
self.tipLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.top.equalToSuperview().offset(44)
make.height.equalTo(17)
}
self.addSubview(self.progressBar)
self.progressBar.snp.makeConstraints { make in
make.top.equalTo(self.snp.top).offset(12 + 44 + 17)
......@@ -76,7 +77,7 @@ extension HomeCollectionViewHeader{
/// 设置头部权限UI是否显示
func setNoPermissionHeaderPage(){
DispatchQueue.main.async {
if Singleton.shared.photoPermission == .authorized {
if PhotoManager.shared.permissionStatus == .authorized {
self.permissionView.isHidden = true
}else {
self.setFileAndCount(count: 0, fileSize: 0)
......@@ -151,9 +152,9 @@ class CustomProgressBar: UIView {
}
}
var totalProgress: CGFloat = 0
var chaoticProgress: CGFloat = 0 {
didSet{
// Print("获取到的资源大小",chaoticProgress)
self.updateProgress()
}
}
......@@ -164,10 +165,11 @@ class CustomProgressBar: UIView {
let disk = WidgetPublicModel.getDiskSpace()
self.totalProgress = Double(disk.0)
self.usedProgress = Double(disk.0) - Double(disk.1)
Task {
let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
self.chaoticProgress = photoData
}
// Task {
// let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
// self.chaoticProgress = photoData
// }
}
......@@ -225,8 +227,9 @@ class CustomProgressBar: UIView {
private func updateProgress() {
// 回到主线程更新 UI
DispatchQueue.main.async {
let usedProgress = CGFloat(self.usedProgress / self.totalProgress)
let chaoticProgress = CGFloat(self.chaoticProgress / self.totalProgress)
let total = self.chaoticProgress + self.totalProgress
let usedProgress = CGFloat((self.usedProgress) / total)
let chaoticProgress = CGFloat(self.chaoticProgress / total)
let totalProgress = usedProgress + chaoticProgress
let remainingProgress = 1 - totalProgress
......
......@@ -122,8 +122,6 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
// self.tipBackView.addSubview(self.saveSizeLabel)
// self.tipBackView.addSubview(self.moreImageView)
self.modelTitlelabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(0)
make.top.equalToSuperview().offset(14)
......@@ -156,6 +154,19 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
make.height.equalTo(20)
}
addSubview(compressionTipView)
compressionTipView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.height.equalTo(80)
make.top.equalTo(sizeLabel.snp.bottom).offset(10)
}
let tap = UITapGestureRecognizer(target: self, action: #selector(compressClick))
compressionTipView.addGestureRecognizer(tap)
// self.tipBackView.snp.makeConstraints { make in
// make.left.equalTo(0)
// make.right.equalTo(0)
......@@ -211,4 +222,22 @@ class HomeVideoDetailCustomHeaderView : UICollectionReusableView {
@objc func sortAction(){
sortCallback()
}
lazy var compressionTipView:VideocompressionHeadView = {
let compressionTipView = Bundle.main.loadNibNamed("VideocompressionHeadView", owner: nil)?.last as! VideocompressionHeadView
return compressionTipView
}()
@objc func compressClick(){
let vc = CompressController()
GETCURRENTNAV()?.pushViewController(vc, animated: true)
}
func GETCURRENTNAV() -> UINavigationController? {
let k = UIApplication.shared.windows.filter({$0.isKeyWindow}).first
let pre = k?.rootViewController?.presentedViewController
let rt = k?.rootViewController
return (pre as? UINavigationController) ?? ((rt as? UITabBarController)?.selectedViewController as? UINavigationController) ?? (rt as? UINavigationController)
}
}
......@@ -344,18 +344,23 @@ class PhotosManagerModel:Codable {
}
}
class HomePhotosModel:Codable {
var folderName:String
var allFileSize:Double
var assets:[[AssetModel]]
init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) {
self.folderName = folderName
self.allFileSize = allFileSize
self.assets = assets
}
}
//class HomePhotosModel:Codable {
//
// var folderName:String
// var allFileSize:Double
// var assets:[[AssetModel]]
//
// init(folderName: String, allFileSize: Double, assets: [[AssetModel]]) {
// self.folderName = folderName
// self.allFileSize = allFileSize
// self.assets = assets
// }
//}
//class AssetModel :Codable,Hashable {
......
//
// VideocompressionHeadView.swift
// PhoneManager
//
// Created by edy on 2025/5/12.
//
import UIKit
class VideocompressionHeadView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 8
layer.masksToBounds = true
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="Jza-pY-4dR" customClass="VideocompressionHeadView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="558" height="148"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_video_compress" translatesAutoresizingMaskIntoConstraints="NO" id="vvL-sB-aRX">
<rect key="frame" x="267" y="8" width="24.333333333333314" height="24"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Video compression" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qxg-Hm-QKr">
<rect key="frame" x="214.66666666666663" y="34" width="129" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="fLR-6a-Hmq"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1GB" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rDm-i5-MN1">
<rect key="frame" x="516" y="0.0" width="42" height="21"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="K4a-jV-Wu7"/>
<constraint firstAttribute="width" constant="42" id="ijO-3x-MyS"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Click to start the process" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YY1-sb-PDU">
<rect key="frame" x="215.66666666666666" y="56" width="126.66666666666666" height="12"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="10"/>
<color key="textColor" red="0.066666666669999999" green="0.066666666669999999" blue="0.066666666669999999" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="JJA-7d-VyB"/>
<color key="backgroundColor" red="0.90196078430000004" green="0.95294117649999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="rDm-i5-MN1" secondAttribute="trailing" id="5LW-mT-Byz"/>
<constraint firstItem="Qxg-Hm-QKr" firstAttribute="top" secondItem="vvL-sB-aRX" secondAttribute="bottom" constant="2" id="B0q-UT-daI"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="centerX" secondItem="Jza-pY-4dR" secondAttribute="centerX" id="C5w-xg-vu7"/>
<constraint firstItem="Qxg-Hm-QKr" firstAttribute="centerX" secondItem="Jza-pY-4dR" secondAttribute="centerX" id="RT9-gS-gWD"/>
<constraint firstItem="YY1-sb-PDU" firstAttribute="centerX" secondItem="Qxg-Hm-QKr" secondAttribute="centerX" id="UKF-3f-8h1"/>
<constraint firstItem="rDm-i5-MN1" firstAttribute="top" secondItem="Jza-pY-4dR" secondAttribute="top" id="Xvi-LX-6zw"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="top" secondItem="Jza-pY-4dR" secondAttribute="top" constant="8" id="oFO-rz-t49"/>
<constraint firstItem="YY1-sb-PDU" firstAttribute="top" secondItem="Qxg-Hm-QKr" secondAttribute="bottom" id="rg1-pq-IPU"/>
<constraint firstItem="vvL-sB-aRX" firstAttribute="centerX" secondItem="Qxg-Hm-QKr" secondAttribute="centerX" id="uEJ-q5-lB3"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="187.78625954198472" y="250"/>
</view>
</objects>
<resources>
<image name="icon_video_compress" width="24.333333969116211" height="24"/>
</resources>
</document>
......@@ -24,6 +24,9 @@ class HomeTitleCollectionCell:UICollectionViewCell {
var homeTititlAction:((_ idx:Int)->Void) = { idx in}
var firstID:String?
var hadLoadFirst:((Bool) ->Void)?
override init(frame: CGRect) {
......@@ -87,7 +90,33 @@ class HomeTitleCollectionCell:UICollectionViewCell {
didSet {
guard let model else {return}
// guard let model else {return}
//
// titleLabel?.text = model.folderName
// titleLabel?.sizeToFit()
//
// var count = 0
//
// for array in model.assets {
//
// count += array.count
// }
//
// fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
// fileLabel?.sizeToFit()
//
// assetsModels = model.homeAssetModel.map({ asset in
// return ImageCollectionModel(asset: asset)
// })
//
// collectionView?.reloadData()
}
}
func reloadUIWithModel(model:HomePhotosModel?){
guard let model = model else { return }
self.model = model
titleLabel?.text = model.folderName
titleLabel?.sizeToFit()
......@@ -101,22 +130,16 @@ class HomeTitleCollectionCell:UICollectionViewCell {
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit()
assetsModels = []
for asset in model.assets.first ?? [] {
let smodel = ImageCollectionModel(asset: asset)
assetsModels.append(smodel)
}
DispatchQueue.main.async {[weak self] in
func reloadCoverData(_ assets:[AssetModel]){
guard let self else {return}
assetsModels.removeAll()
assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model)
})
self.collectionView?.reloadData()
}
}
collectionView?.reloadData()
}
override func layoutSubviews() {
......@@ -142,14 +165,16 @@ class HomeTitleCollectionCell:UICollectionViewCell {
make.left.equalToSuperview().offset(16)
make.bottom.equalToSuperview().offset(-16)
make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32)
make.height.equalToSuperview().offset(-64)
make.width.equalTo(width-32)
make.height.equalTo(height-64)
// make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32)
// make.height.equalToSuperview().offset(-64)
})
nextImage?.snp.makeConstraints({ make in
make.centerY.equalTo(fileLabel!)
make.right.equalToSuperview().offset(-12)
make.right.equalTo(-12)
make.width.height.equalTo(20)
})
}
......
This diff is collapsed.
......@@ -10,5 +10,9 @@ import Foundation
extension NSNotification.Name {
//垃圾桶分页页面滑动监听
static let trashPageScroll: NSNotification.Name = NSNotification.Name(rawValue: "trashPageScroll")
//监听拿到基本相册资源
static let getBaseAssetsSuccess: NSNotification.Name = NSNotification.Name(rawValue: "getBaseAssetsSuccess")
}
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