Commit b7289b40 authored by yqz's avatar yqz

Merge branch 'develop' into sections

* develop:
  压缩更新
  修改压缩逻辑
parents 75f590b9 9100f541
......@@ -6,6 +6,7 @@
//
import Foundation
import Photos
typealias CompressSelectCellCallback = (ResourceModel,Bool)->Void
......@@ -13,18 +14,46 @@ class CompressSelectCell : UICollectionViewCell {
var callBack : CompressSelectCellCallback = {model,choose in}
var currentMediaType : Int = 0
var model : ResourceModel? {
didSet{
guard let model = self.model else {return}
let image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: model.ident)
self.backImageView.image = image
self.backImageView.image = nil
let viewModel = CompressViewModel()
viewModel.getImageFromAssetIdentifier(identifier: model.ident) {[weak self] image in
guard let self else { return}
DispatchQueue.main.async {
self.backImageView.image = image
}
}
// 把压缩前的值减去压缩后的值就为可以节省的值。然后这里需要判定下如果是大于1000MB,则再除以1024换算成GB
let saveSize = model.orgSize - model.compressSize
if saveSize > 1000 {
self.saveSizeLabel.text = String(format: "Save %.2f GB" ,saveSize/1024.0)
}else{
self.saveSizeLabel.text = String(format: "Save %.2f MB" ,saveSize)
let options = PHImageRequestOptions()
options.isSynchronous = false
options.deliveryMode = .highQualityFormat
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [model.ident], options: nil)
PHImageManager.default().requestImage(for: fetchResult.firstObject!, targetSize: PHImageManagerMaximumSize, contentMode:.aspectFit, options: options) { (image, _) in
if let originalImage = image {
// 项目中用到的是【0.2、0.5和0.8】,这里我们初始化的时候使用0.2去计算
if let compressedData = originalImage.jpegData(compressionQuality: 0.2) {
let compressCompletedSize = Double(compressedData.count)
let saveSize = model.orgSize - compressCompletedSize
let sizeKB : Double = saveSize/1024
DispatchQueue.main.async {
if sizeKB < 1024{
self.saveSizeLabel.text = String(format: "Save %.2f KB" ,sizeKB)
}else if sizeKB < (1024 * 1024) && sizeKB > 1024{
self.saveSizeLabel.text = String(format: "Save %.2f MB" ,sizeKB/1024)
}else{
self.saveSizeLabel.text = String(format: "Save %.2f GB" ,sizeKB/(1024 * 1024))
}
}
}
}
}
}
}
......@@ -96,7 +125,12 @@ class CompressSelectCell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
super.init(frame: frame)
self.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(imageClick))
self.addGestureRecognizer(tap)
self.addSubview(self.backImageView)
self.addSubview(self.saveSizeView)
......@@ -110,14 +144,14 @@ class CompressSelectCell : UICollectionViewCell {
self.saveSizeView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.bottom.equalToSuperview().offset(-12)
make.height.equalTo(48)
make.height.equalTo(25)
make.width.equalTo(120)
}
self.saveSizeLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.centerY.equalToSuperview()
make.height.equalTo(48)
make.width.equalTo(120)
make.height.equalTo(25)
make.width.equalTo(105)
}
self.moreImageView.snp.makeConstraints { make in
......@@ -139,4 +173,18 @@ class CompressSelectCell : UICollectionViewCell {
fatalError("init(coder:) has not been implemented")
}
@objc func imageClick(){
if self.currentMediaType == 0 {
// 如果是图片
let vc : PreViewController = PreViewController()
vc.imageIdent = self.model!.ident
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}else{
// 如果是视频
let vc : PreVideoController = PreVideoController(localIdentifier: self.model!.ident)
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
}
}
......@@ -6,11 +6,18 @@
//
import Foundation
import Photos
class CompressCompletedViewController : BaseViewController{
var model : [ResourceModel]?
var comDataSource : [Data] = []
var comVideoDataSource : [URL?] = []
var currentMediaType : Int = 0
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
......@@ -248,24 +255,90 @@ class CompressCompletedViewController : BaseViewController{
}
@objc func completedAction(){
if currentMediaType == 0 {
// 将压缩后的照片存到相册
for imageData in self.comDataSource {
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: imageData, options: nil)
}) { success, error in
if(success){
print("保存照片成功")
}else {
if let error = error {
print("保存相片时出错: \(error.localizedDescription)")
}
}
}
}
}else{
for item in self.comVideoDataSource {
guard let item else {
print("保存视频失败,URL为空")
self.jumpToCompressVC()
return
}
// 保存视频到相册
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: item as URL)
}) { (success, saveError) in
if success {
print("保存视频成功")
}else{
if let error = saveError {
print("保存视频时出错: \(error.localizedDescription)")
}
}
}
}
}
// 提示是否删除照片
let compressTipView : CompressTipView = CompressTipView(frame: self.view.bounds)
self.view.addSubview(compressTipView)
compressTipView.callBack = {[weak self] allow in
guard let self else {return}
// 删除文件逻辑【系统自动提示是否删除】
var count = 0
for data in self.model! {
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [data.ident], options: nil)
let assetToDelete = fetchResult.firstObject
PHPhotoLibrary.shared().performChanges ({
PHAssetChangeRequest.deleteAssets([assetToDelete] as NSFastEnumeration)
}){ success, error in
if(success){
self.updateCompressData(flag: data.ident)
print("删除文件成功")
}else {
if let error = error {
print("删除文件时出错: \(error.localizedDescription)")
}
}
count = count + 1
if count == self.model?.count{
self.jumpToCompressVC()
}
}
}
}
func jumpToCompressVC(){
DispatchQueue.main.async {
if let targetVC = self.navigationController?.viewControllers.first(where: { $0 is CompressController }) {
self.navigationController?.popToViewController(targetVC, animated: true)
}
// 如果不允许就两张都放进去,否则异步删除照片
if allow as! Bool == false {
// 删除照片逻辑
}
}
}
/// 更新数据
/// - Parameter flag: 图片的ident
func updateCompressData(flag : String){
DispatchQueue.main.async {
// 移除VC中的数据
let compressVC = self.navigationController?.viewControllers.first(where: { $0 is CompressController }) as! CompressController as CompressController
compressVC.resourceData.removeAll { $0.ident == flag }
// 移除单利中的数据
Singleton.shared.resourceModel.removeAll{ $0.ident == flag }
}
}
override func viewDidLoad() {
super.viewDidLoad()
......
......@@ -96,19 +96,42 @@ class CompressController : BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
self.navigationController?.navigationBar.isHidden = true
getViewData()
setUI()
}
/// 获取当前页面数据
func getViewData(){
self.resourceData.removeAll()
let datas = Singleton.shared.resourceModel
if datas.count > 0 {
// 这里需要重新排序下
if self.currentSort == 0 {
self.resourceData = datas.sorted {$0.orgSize > $1.orgSize }
}else if self.currentSort == 1 {
self.resourceData = datas.sorted {$0.orgSize < $1.orgSize }
}else if self.currentSort == 2 {
self.resourceData = datas.sorted {$0.createDate > $1.createDate }
}else{
self.resourceData = datas.sorted {$0.createDate < $1.createDate }
}
}else{
CompressViewModel().getAllPhotosToAssets(sortType: self.currentSort, assetType: self.currentResourceType) { [weak self] models in
guard let self else {return}
self.resourceData = models
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 这里默认去请求下当前的数据资源
let viewModel = CompressViewModel()
viewModel.getAllPhotos {[weak self] models in
guard let self else {return}
self.resourceData = models
// 目的是为了消除cell 的选择按钮状态
if self.selectedModel.count == 0 {
self.collectionView.reloadData()
}
}
}
......@@ -130,6 +153,7 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CompressSelectCell", for: indexPath) as! CompressSelectCell
cell.model = self.resourceData[indexPath.row]
cell.currentMediaType = self.currentResourceType
if self.selectedModel.count == 0 {
cell.choose = false
}
......@@ -207,6 +231,23 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
self.sortByType(sortType: self.currentSort, header: header)
}
}
header.changeView.callBack = {[weak self] flag in
guard let self else {return}
if self.currentResourceType != flag as! Int {
self.currentResourceType = flag as! Int
// 先移除下,防止点到
self.resourceData.removeAll()
// 如果是图片,直接从缓存中加载
if self.currentResourceType == 0 {
self.getViewData()
}else{
CompressViewModel().getAllPhotosToAssets(sortType: self.currentSort, assetType: flag as! Int) { [weak self] models in
guard let self else {return}
self.resourceData = models
}
}
}
}
header.modeData = self.resourceData
return header
}else{
......@@ -275,6 +316,7 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
// 先将值传到下一个页面
let vc : CompressQualityController = CompressQualityController()
vc.model = self.selectedModel
vc.currentMediaType = self.currentResourceType
vc.detailTiplabel.text = "You've selected \(self.selectedModel.count) out of \(self.resourceData.count) photos to compress."
self.navigationController?.pushViewController(vc, animated: true)
......@@ -288,4 +330,6 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
self.selectedModel.removeAll()
self.updateSubmitButton()
}
}
......@@ -20,6 +20,8 @@ class CompressQualityController : BaseViewController{
var currentQulityType : Int = 0
var currentMediaType : Int = 0
private var compressNav:CompressNavView?
......@@ -179,6 +181,40 @@ class CompressQualityController : BaseViewController{
}
}
fileprivate func updateNextView(_ compressAllSize: Double, _ compressingView: CompressingView,_ comDataSource : [Data],_ comVideoDataSource : [URL?]) {
DispatchQueue.main.async {
compressingView.removeFromSuperview()
let vc = CompressCompletedViewController()
vc.currentMediaType = self.currentMediaType
vc.comDataSource = comDataSource
vc.comVideoDataSource = comVideoDataSource
vc.model = self.model
vc.detailTipToplabel.text = "\(self.model!.count) items"
vc.detailTipBottomlabel.text = "\(self.model!.count) items"
var sum = 0.0
var orgAllSize = 0.0
for modelData in self.model! {
orgAllSize = orgAllSize + modelData.orgSize
}
sum = orgAllSize - compressAllSize
sum = sum / 1024
if sum < 1024{
vc.sizeToplabel.text = String(format:"%.2lfKB",sum)
}else if sum < (1024 * 1024) && sum > 1024{
sum = sum / 1024
vc.sizeToplabel.text = String(format:"%.2lfMB",sum)
}else{
sum = sum / (1024 * 1024)
vc.sizeToplabel.text = String(format:"%.2lfGB",sum)
}
let str = String(format:"%.2lf",(orgAllSize - compressAllSize) / orgAllSize)
vc.sizeBottomlabel.text = "\(str)%"
self.navigationController?.pushViewController(vc, animated: true)
}
}
@objc func submitAction(){
let compressingView : CompressingView = CompressingView(frame: self.view.bounds)
......@@ -195,77 +231,55 @@ class CompressQualityController : BaseViewController{
if currentQulityType == 2 {
currentQulity = 0.8
}
manager.compress(assets: self.model!, compressionQuality: currentQulity) {progress in
compressingView.animationView.setProgress(CGFloat(progress), animated: true, duration: 0.1)
} completion: { compressedDataArray, errorArray in
for (index, data) in compressedDataArray.enumerated() {
if let error = errorArray[index] {
print("第 \(index + 1) 张图片压缩出错: \(error.localizedDescription)")
} else if let data = data {
print("第 \(index + 1) 张图片压缩完成,压缩后大小: \(data.count) 字节")
} else {
print("第 \(index + 1) 张图片压缩失败")
}
}
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
compressingView.removeFromSuperview()
let vc = CompressCompletedViewController()
vc.model = self.model
vc.detailTipToplabel.text = "\(self.model!.count) items"
vc.detailTipBottomlabel.text = "\(self.model!.count) items"
var sum = 0.0
var orgAllSize = 0.0
var comDataSource : [Data] = []
if self.currentMediaType == 0 {
// 表示压缩图片
manager.compress(assets: self.model!, compressionQuality: currentQulity) {progress in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { compressedDataArray, errorArray in
var compressAllSize = 0.0
for modelData in self.model! {
orgAllSize = orgAllSize + modelData.orgSize
compressAllSize = compressAllSize + modelData.compressSize
sum = sum + modelData.orgSize - modelData.compressSize
for (index, data) in compressedDataArray.enumerated() {
if let error = errorArray[index] {
print("第 \(index + 1) 个文件压缩出错: \(error.localizedDescription)")
} else if let data = data {
print("第 \(index + 1) 个文件压缩完成,压缩后大小: \(data.count) 字节")
compressAllSize = compressAllSize + Double(data.count)
comDataSource.append(data)
} else {
print("第 \(index + 1) 个文件压缩失败")
}
}
if sum > 1000 {
sum = sum / 1024
vc.sizeToplabel.text = "\(sum)GB"
}else{
vc.sizeToplabel.text = "\(sum)MB"
self.updateNextView(compressAllSize,compressingView,comDataSource,[])
}
}else{
// 压缩视频
var compressAllSize : Double = 0.0
manager.compressVideos(models: self.model!, quality: Float(currentQulity)) { (identifier, progress) in
compressingView.animationView.setProgress(CGFloat(progress), animated: false, duration: 0.1)
} completion: { (outputURLs, errors) in
for (index, outputURL) in outputURLs.enumerated() {
if let outputURL = outputURL {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
if let fileSize = attributes[.size] as? Int64 {
compressAllSize = compressAllSize + Double(fileSize)
}
} catch {
Print("获取视频文件大小失败")
}
print("Compressed video \(index) saved at: \(outputURL)")
} else if let error = errors[index] {
print("Error compressing video \(index): \(error.localizedDescription)")
}
}
let str = String(format:"%.2lf",(orgAllSize - compressAllSize) / orgAllSize)
vc.sizeBottomlabel.text = "\(str)%"
self.navigationController?.pushViewController(vc, animated: true)
self.updateNextView(compressAllSize,compressingView,[],outputURLs)
}
}
// DispatchQueue.main.async {
// compressingView.removeFromSuperview()
// let vc = CompressCompletedViewController()
// vc.model = self.model
// vc.detailTipToplabel.text = "\(self.model!.count) items"
// vc.detailTipBottomlabel.text = "\(self.model!.count) items"
//
// var sum = 0.0
// var orgAllSize = 0.0
// var compressAllSize = 0.0
// for modelData in self.model! {
// orgAllSize = orgAllSize + modelData.orgSize
// compressAllSize = compressAllSize + modelData.compressSize
// sum = sum + modelData.orgSize - modelData.compressSize
// }
// if sum > 1000 {
// sum = sum / 1024
// vc.sizeToplabel.text = "\(sum)GB"
// }else{
// vc.sizeToplabel.text = "\(sum)MB"
// }
// let str = String(format:"%.2lf",(orgAllSize - compressAllSize) / orgAllSize)
// vc.sizeBottomlabel.text = "\(str)%"
//
//
// self.navigationController?.pushViewController(vc, animated: true)
// }
}
}
//
// PreVideoController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/6.
//
import Foundation
import AVFoundation
import Photos
class PreVideoController : BaseViewController {
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
private let localIdentifier: String
init(localIdentifier: String) {
self.localIdentifier = localIdentifier
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupVideoPlayer()
setupTapGesture()
}
private func setupVideoPlayer() {
guard let asset = fetchAsset() else { return }
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { [weak self] (avAsset, _, _) in
guard let self = self, let avAsset = avAsset as? AVURLAsset else { return }
DispatchQueue.main.async {
self.player = AVPlayer(url: avAsset.url)
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer?.frame = CGRect(x: 0, y: self.titleView.height, width: self.view.width, height: self.view.height - self.titleView.height)
self.view.layer.addSublayer(self.playerLayer!)
self.player?.play()
}
}
}
private func fetchAsset() -> PHAsset? {
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
return fetchResult.firstObject
}
private func setupTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc private func handleTap() {
if presentedViewController != nil {
dismiss(animated: true, completion: nil)
} else {
navigationController?.popViewController(animated: true)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
player?.pause()
}
}
//
// PreViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/6.
//
import Foundation
class PreViewController : BaseViewController {
var imageIdent : String = ""
lazy var preImageView : UIImageView = {
let view = UIImageView(frame: CGRect(x: 0, y: self.titleView.height, width: self.view.width, height: self.view.height - self.titleView.height))
view.backgroundColor = .white
view.image = PhotoAndVideoMananger.mananger.getImageFromAssetID(id: self.imageIdent)
view.contentMode = .scaleAspectFit
view.clipsToBounds = true
view.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(selectClick))
view.addGestureRecognizer(tap)
return view
}()
@objc func selectClick(){
self.navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.preImageView)
}
}
......@@ -10,16 +10,12 @@ import Photos
struct ResourceModel : Equatable{
var ident : String
var compressSize : Double
var orgSize: Double
var createDate : Date
var imageAsset: PHAsset
init(ident: String, compressSize: Double, orgSize: Double, createDate: Date, imageAsset: PHAsset) {
init(ident: String, orgSize: Double, createDate: Date) {
self.ident = ident
self.compressSize = compressSize
self.orgSize = orgSize
self.createDate = createDate
self.imageAsset = imageAsset
}
}
......@@ -19,18 +19,24 @@ class CompressCustomHeaderView: UICollectionReusableView{
var saveSum = 0.0
for model in self.modeData{
sum = sum + model.orgSize
saveSum = saveSum + (model.orgSize - model.compressSize)
saveSum = saveSum + model.orgSize * 0.8
}
if sum > 1000 {
self.siezLabel.text = String(format: "%.2f GB" ,(sum/1024))
sum = sum / 1000
saveSum = saveSum / 1024
if sum < 1024 {
self.siezLabel.text = String(format: "%.2f KB" ,(sum))
}else if sum < (1024 * 1024) && sum > 1024{
self.siezLabel.text = String(format: "%.2f MB" ,(sum/1024))
}else{
self.siezLabel.text = String(format: "%.2f MB" ,(sum))
self.siezLabel.text = String(format: "%.2f GB" ,sum/(1024*1024))
}
if saveSum > 1000 {
self.saveSizeLabel.text = String(format: "%.2f GB" ,(saveSum/1024.0))
}else {
self.saveSizeLabel.text = String(format: "%.2f MB" ,(saveSum))
if saveSum < 1024 {
self.saveSizeLabel.text = String(format: "%.2f KB" ,(saveSum))
}else if saveSum < (1024 * 1024) && saveSum > 1024{
self.saveSizeLabel.text = String(format: "%.2f MB" ,(saveSum/1024))
}else{
self.saveSizeLabel.text = String(format: "%.2f GB" ,saveSum/(1024*1024))
}
}
......@@ -152,7 +158,7 @@ class CompressCustomHeaderView: UICollectionReusableView{
self.selectlabel.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-12)
make.top.equalToSuperview().offset(6)
make.width.equalTo(51 * RScreenW())
make.width.equalTo(60 * RScreenW())
make.height.equalTo(20)
}
self.siezLabel.snp.makeConstraints { make in
......@@ -196,6 +202,7 @@ class CompressCustomHeaderView: UICollectionReusableView{
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
setUI()
......
......@@ -98,6 +98,12 @@ class CompressSortView : UIView,UITableViewDelegate,UITableViewDataSource {
lazy var backView : UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
view.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(backViewClick))
view.addGestureRecognizer(tap)
return view
}()
......@@ -154,6 +160,9 @@ class CompressSortView : UIView,UITableViewDelegate,UITableViewDataSource {
// 移除自身
self.removeFromSuperview()
}
@objc func backViewClick(){
// 移除自身
self.removeFromSuperview()
}
}
......@@ -9,6 +9,8 @@ import Foundation
class CompressSwitchView : UIView {
var callBack: callBack<Any> = {flag in }
var currentButton : UIButton?
lazy var leftButton : UIButton = {
......@@ -18,6 +20,7 @@ class CompressSwitchView : UIView {
button.setTitle("Photos", for: .normal)
button.addTarget(self, action: #selector(selectedButtonAction(_:)), for: .touchUpInside)
button.setTitleColor(UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1), for: .normal)
button.tag = 1000
return button
}()
......@@ -28,6 +31,7 @@ class CompressSwitchView : UIView {
button.setTitle("Videos", for: .normal)
button.addTarget(self, action: #selector(selectedButtonAction(_:)), for: .touchUpInside)
button.setTitleColor(UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1), for: .normal)
button.tag = 1001
return button
}()
......@@ -74,6 +78,8 @@ class CompressSwitchView : UIView {
sender.setTitleColor(UIColor(red: 0, green: 0.51, blue: 1, alpha: 1), for: .normal)
sender.backgroundColor = .white
self.currentButton = sender
self.callBack(sender.tag - 1000)
}
}
......@@ -86,7 +86,7 @@ class CompressTipView : UIView {
lazy var tipLabel: UILabel = {
let label = UILabel()
label.text = "Allow ”Al Cleaner“ to delete2 photos?"
label.text = "Allow ”Al Cleaner“ to delete 2 photos?"
label.textAlignment = .center
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 17, weight: .bold)
......
//
// Singleton.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/6.
//
import Foundation
class Singleton {
// 使用静态常量来保存单例实例
static let shared = Singleton()
// 私有化初始化方法,防止外部创建新实例
private init() {}
var resourceModel : [ResourceModel] = []
}
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