feat: add travel album module
This commit is contained in:
479
suixinkan/UI/TravelAlbum/TravelAlbumDetailViewController.swift
Normal file
479
suixinkan/UI/TravelAlbum/TravelAlbumDetailViewController.swift
Normal file
@ -0,0 +1,479 @@
|
||||
//
|
||||
// TravelAlbumDetailViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 相册详情管理页,对齐 Android `TravelAlbumDetailScreen`。
|
||||
final class TravelAlbumDetailViewController: BaseViewController {
|
||||
private let viewModel: TravelAlbumDetailViewModel
|
||||
private let api: any TravelAlbumServing
|
||||
|
||||
private let scrollContainer = UIView()
|
||||
private let infoCard = TravelAlbumInfoCard()
|
||||
private let manageCard = UIView()
|
||||
private let tabStack = UIStackView()
|
||||
private let allTabButton = UIButton(type: .system)
|
||||
private let purchasedTabButton = UIButton(type: .system)
|
||||
private let sortButton = UIButton(type: .system)
|
||||
private let selectButton = UIButton(type: .system)
|
||||
private var collectionView: UICollectionView!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Int, TravelAlbumMaterial>!
|
||||
private let bottomBar = UIView()
|
||||
private let deleteSelectedButton = UIButton(type: .system)
|
||||
private let uploadButton = UIButton(type: .system)
|
||||
|
||||
init(albumId: Int, api: any TravelAlbumServing = NetworkServices.shared.travelAlbumAPI) {
|
||||
viewModel = TravelAlbumDetailViewModel(albumId: albumId)
|
||||
self.api = api
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "相册管理"
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
image: UIImage(systemName: "ellipsis"),
|
||||
menu: UIMenu(children: [
|
||||
UIAction(title: "删除相册", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] _ in
|
||||
self?.confirmDeleteAlbum()
|
||||
},
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackground
|
||||
|
||||
manageCard.backgroundColor = .white
|
||||
manageCard.layer.cornerRadius = 12
|
||||
manageCard.layer.shadowColor = UIColor.black.withAlphaComponent(0.08).cgColor
|
||||
manageCard.layer.shadowOpacity = 1
|
||||
manageCard.layer.shadowRadius = 6
|
||||
manageCard.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
|
||||
tabStack.axis = .horizontal
|
||||
tabStack.spacing = 8
|
||||
configurePillButton(allTabButton)
|
||||
configurePillButton(purchasedTabButton)
|
||||
configureIconButton(sortButton, image: UIImage(systemName: "line.3.horizontal.decrease.circle.fill"))
|
||||
configureIconButton(selectButton, image: UIImage(systemName: "circle"))
|
||||
|
||||
collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeLayout())
|
||||
collectionView.backgroundColor = .white
|
||||
collectionView.delegate = self
|
||||
collectionView.register(TravelAlbumMaterialCell.self, forCellWithReuseIdentifier: TravelAlbumMaterialCell.reuseIdentifier)
|
||||
dataSource = UICollectionViewDiffableDataSource<Int, TravelAlbumMaterial>(collectionView: collectionView) {
|
||||
[weak self] collectionView, indexPath, material in
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: TravelAlbumMaterialCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! TravelAlbumMaterialCell
|
||||
cell.apply(
|
||||
material: material,
|
||||
selectionMode: self?.viewModel.isSelectionMode == true,
|
||||
selected: self?.viewModel.selectedMaterialIds.contains(material.id) == true
|
||||
)
|
||||
return cell
|
||||
}
|
||||
|
||||
bottomBar.backgroundColor = AppColor.pageBackground
|
||||
configureBottomButton(deleteSelectedButton, title: "删除选中(0)", color: UIColor(hex: 0xE53935))
|
||||
configureBottomButton(uploadButton, title: "上传照片", color: AppColor.primary)
|
||||
deleteSelectedButton.isHidden = true
|
||||
|
||||
view.addSubview(scrollContainer)
|
||||
scrollContainer.addSubview(infoCard)
|
||||
scrollContainer.addSubview(manageCard)
|
||||
manageCard.addSubview(tabStack)
|
||||
tabStack.addArrangedSubview(allTabButton)
|
||||
tabStack.addArrangedSubview(purchasedTabButton)
|
||||
manageCard.addSubview(sortButton)
|
||||
manageCard.addSubview(selectButton)
|
||||
manageCard.addSubview(collectionView)
|
||||
view.addSubview(bottomBar)
|
||||
bottomBar.addSubview(deleteSelectedButton)
|
||||
bottomBar.addSubview(uploadButton)
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
bottomBar.snp.makeConstraints { make in
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
deleteSelectedButton.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(12)
|
||||
make.leading.trailing.equalToSuperview().inset(20)
|
||||
make.height.equalTo(48)
|
||||
}
|
||||
uploadButton.snp.makeConstraints { make in
|
||||
make.top.equalTo(deleteSelectedButton.snp.bottom).offset(8)
|
||||
make.leading.trailing.equalTo(deleteSelectedButton)
|
||||
make.height.equalTo(48)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
||||
}
|
||||
scrollContainer.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(12)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.bottom.equalTo(bottomBar.snp.top).offset(-8)
|
||||
}
|
||||
infoCard.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview()
|
||||
make.height.greaterThanOrEqualTo(76)
|
||||
}
|
||||
manageCard.snp.makeConstraints { make in
|
||||
make.top.equalTo(infoCard.snp.bottom).offset(12)
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
tabStack.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(12)
|
||||
make.height.equalTo(32)
|
||||
}
|
||||
sortButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(tabStack)
|
||||
make.trailing.equalTo(selectButton.snp.leading).offset(-8)
|
||||
make.size.equalTo(32)
|
||||
}
|
||||
selectButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(tabStack)
|
||||
make.trailing.equalToSuperview().offset(-12)
|
||||
make.size.equalTo(32)
|
||||
}
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.top.equalTo(tabStack.snp.bottom).offset(12)
|
||||
make.leading.trailing.bottom.equalToSuperview().inset(12)
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
infoCard.onCall = { [weak self] phone in self?.call(phone) }
|
||||
allTabButton.addTarget(self, action: #selector(allTabTapped), for: .touchUpInside)
|
||||
purchasedTabButton.addTarget(self, action: #selector(purchasedTabTapped), for: .touchUpInside)
|
||||
sortButton.addTarget(self, action: #selector(sortTapped), for: .touchUpInside)
|
||||
selectButton.addTarget(self, action: #selector(selectTapped), for: .touchUpInside)
|
||||
deleteSelectedButton.addTarget(self, action: #selector(deleteSelectedTapped), for: .touchUpInside)
|
||||
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in self?.applyViewModel() }
|
||||
}
|
||||
viewModel.onShowMessage = { [weak self] message in
|
||||
Task { @MainActor in self?.showToast(message) }
|
||||
}
|
||||
viewModel.onDeletedAlbum = { [weak self] _ in
|
||||
Task { @MainActor in self?.navigationController?.popViewController(animated: true) }
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
Task { await viewModel.refreshAll(api: api) }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func applyViewModel() {
|
||||
if let album = viewModel.album {
|
||||
infoCard.apply(album: album)
|
||||
}
|
||||
configureTabButton(allTabButton, title: "全部照片\(viewModel.allPhotoCount)", selected: viewModel.selectedTab == .all)
|
||||
configureTabButton(purchasedTabButton, title: "已购照片", selected: viewModel.selectedTab == .purchased)
|
||||
selectButton.isHidden = viewModel.selectedTab != .all
|
||||
selectButton.setImage(UIImage(systemName: viewModel.isSelectionMode ? "checkmark.circle.fill" : "circle"), for: .normal)
|
||||
selectButton.tintColor = viewModel.isSelectionMode ? AppColor.primary : AppColor.textTertiary
|
||||
deleteSelectedButton.isHidden = !(viewModel.isSelectionMode && !viewModel.selectedMaterialIds.isEmpty)
|
||||
deleteSelectedButton.setTitle("删除选中(\(viewModel.selectedMaterialIds.count))", for: .normal)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, TravelAlbumMaterial>()
|
||||
snapshot.appendSections([0])
|
||||
snapshot.appendItems(viewModel.materials)
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
|
||||
if viewModel.isLoading && viewModel.album == nil {
|
||||
showLoading()
|
||||
} else {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private func configurePillButton(_ button: UIButton) {
|
||||
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
|
||||
button.layer.cornerRadius = 16
|
||||
button.contentEdgeInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14)
|
||||
}
|
||||
|
||||
private func configureTabButton(_ button: UIButton, title: String, selected: Bool) {
|
||||
button.setTitle(title, for: .normal)
|
||||
button.backgroundColor = selected ? AppColor.primary : UIColor(hex: 0xEAF4FF)
|
||||
button.setTitleColor(selected ? .white : AppColor.primary, for: .normal)
|
||||
}
|
||||
|
||||
private func configureIconButton(_ button: UIButton, image: UIImage?) {
|
||||
button.setImage(image, for: .normal)
|
||||
button.backgroundColor = UIColor(hex: 0xEAF4FF)
|
||||
button.tintColor = AppColor.primary
|
||||
button.layer.cornerRadius = 16
|
||||
}
|
||||
|
||||
private func configureBottomButton(_ button: UIButton, title: String, color: UIColor) {
|
||||
button.setTitle(title, for: .normal)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
button.backgroundColor = color
|
||||
button.layer.cornerRadius = 10
|
||||
}
|
||||
|
||||
private func makeLayout() -> UICollectionViewCompositionalLayout {
|
||||
UICollectionViewCompositionalLayout { _, environment in
|
||||
let spacing: CGFloat = 8
|
||||
let width = (environment.container.effectiveContentSize.width - spacing * 2) / 3
|
||||
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(width), heightDimension: .absolute(width + 38))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(width + 38))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 3)
|
||||
group.interItemSpacing = .fixed(spacing)
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.interGroupSpacing = 12
|
||||
return section
|
||||
}
|
||||
}
|
||||
|
||||
private func call(_ phone: String) {
|
||||
guard !phone.isEmpty, let url = URL(string: "tel:\(phone)") else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
private func confirmDeleteAlbum() {
|
||||
let alert = UIAlertController(title: "删除相册", message: "确定删除该相册吗?已有购买素材的相册无法删除。", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "删除", style: .destructive) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task { await self.viewModel.deleteAlbum(api: self.api) }
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func allTabTapped() {
|
||||
Task { await viewModel.selectTab(.all, api: api) }
|
||||
}
|
||||
|
||||
@objc private func purchasedTabTapped() {
|
||||
Task { await viewModel.selectTab(.purchased, api: api) }
|
||||
}
|
||||
|
||||
@objc private func sortTapped() {
|
||||
let alert = UIAlertController(title: "排序", message: nil, preferredStyle: .actionSheet)
|
||||
TravelAlbumDetailViewModel.SortOption.allCases.forEach { option in
|
||||
alert.addAction(UIAlertAction(title: option.title, style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task { await self.viewModel.setSortOption(option, api: self.api) }
|
||||
})
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func selectTapped() {
|
||||
viewModel.toggleSelectionMode()
|
||||
}
|
||||
|
||||
@objc private func deleteSelectedTapped() {
|
||||
let count = viewModel.selectedMaterialIds.count
|
||||
let alert = UIAlertController(title: "删除素材", message: "确定删除选中的 \(count) 张素材吗?", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "删除", style: .destructive) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task { await self.viewModel.deleteSelectedMaterials(api: self.api) }
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func uploadTapped() {
|
||||
let album = viewModel.album
|
||||
let controller = WiredCameraTransferViewController(
|
||||
viewModel: WiredCameraTransferViewModel(
|
||||
albumId: album?.id ?? viewModel.albumId,
|
||||
albumTitle: album?.name ?? "",
|
||||
headerPhone: album?.displayPhone ?? ""
|
||||
)
|
||||
)
|
||||
navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension TravelAlbumDetailViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let material = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
viewModel.toggleMaterialSelection(material)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let visible = collectionView.indexPathsForVisibleItems.map(\.item).max() ?? 0
|
||||
Task { await viewModel.loadMoreIfNeeded(lastVisibleIndex: visible, api: api) }
|
||||
}
|
||||
}
|
||||
|
||||
/// 旅拍相册详情信息卡。
|
||||
private final class TravelAlbumInfoCard: UIView {
|
||||
var onCall: ((String) -> Void)?
|
||||
private var phone = ""
|
||||
|
||||
private let iconView = UIImageView(image: UIImage(systemName: "checklist"))
|
||||
private let phoneLabel = UILabel()
|
||||
private let timeLabel = UILabel()
|
||||
private let callButton = UIButton(type: .system)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 12
|
||||
layer.shadowColor = UIColor.black.withAlphaComponent(0.08).cgColor
|
||||
layer.shadowOpacity = 1
|
||||
layer.shadowRadius = 6
|
||||
layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
|
||||
let iconBox = UIView()
|
||||
iconBox.backgroundColor = AppColor.primary
|
||||
iconBox.layer.cornerRadius = 10
|
||||
iconView.tintColor = .white
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
phoneLabel.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
phoneLabel.textColor = AppColor.textPrimary
|
||||
timeLabel.font = .systemFont(ofSize: 12)
|
||||
timeLabel.textColor = AppColor.textTertiary
|
||||
callButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
|
||||
callButton.tintColor = AppColor.primary
|
||||
callButton.backgroundColor = UIColor(hex: 0xEAF4FF)
|
||||
callButton.layer.cornerRadius = 20
|
||||
|
||||
addSubview(iconBox)
|
||||
iconBox.addSubview(iconView)
|
||||
addSubview(phoneLabel)
|
||||
addSubview(timeLabel)
|
||||
addSubview(callButton)
|
||||
iconBox.snp.makeConstraints { make in
|
||||
make.leading.top.bottom.equalToSuperview().inset(14)
|
||||
make.size.equalTo(48)
|
||||
}
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.size.equalTo(28)
|
||||
}
|
||||
phoneLabel.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(16)
|
||||
make.leading.equalTo(iconBox.snp.trailing).offset(12)
|
||||
make.trailing.equalTo(callButton.snp.leading).offset(-12)
|
||||
}
|
||||
timeLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(phoneLabel.snp.bottom).offset(6)
|
||||
make.leading.trailing.equalTo(phoneLabel)
|
||||
make.bottom.equalToSuperview().offset(-16)
|
||||
}
|
||||
callButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-14)
|
||||
make.centerY.equalToSuperview()
|
||||
make.size.equalTo(40)
|
||||
}
|
||||
callButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(album: TravelAlbum) {
|
||||
phone = album.displayPhone
|
||||
phoneLabel.text = "手机号 \(TravelAlbumDisplayFormatter.maskPhone(album.displayPhone))"
|
||||
timeLabel.text = "创建时间 \(album.createdAt)"
|
||||
}
|
||||
|
||||
@objc private func callTapped() {
|
||||
onCall?(phone)
|
||||
}
|
||||
}
|
||||
|
||||
/// 旅拍相册素材网格 cell。
|
||||
private final class TravelAlbumMaterialCell: UICollectionViewCell {
|
||||
static let reuseIdentifier = "TravelAlbumMaterialCell"
|
||||
|
||||
private let imageView = UIImageView()
|
||||
private let statusLabel = UILabel()
|
||||
private let checkImageView = UIImageView()
|
||||
private let nameLabel = UILabel()
|
||||
private let sizeLabel = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.clipsToBounds = true
|
||||
imageView.layer.cornerRadius = 8
|
||||
statusLabel.text = "已上传"
|
||||
statusLabel.font = .systemFont(ofSize: 10)
|
||||
statusLabel.textColor = .white
|
||||
statusLabel.backgroundColor = UIColor(hex: 0x34C759)
|
||||
statusLabel.layer.cornerRadius = 4
|
||||
statusLabel.clipsToBounds = true
|
||||
statusLabel.textAlignment = .center
|
||||
checkImageView.tintColor = .white
|
||||
checkImageView.backgroundColor = UIColor.black.withAlphaComponent(0.35)
|
||||
checkImageView.layer.cornerRadius = 10
|
||||
nameLabel.font = .systemFont(ofSize: 11)
|
||||
nameLabel.textColor = AppColor.textPrimary
|
||||
nameLabel.lineBreakMode = .byTruncatingMiddle
|
||||
sizeLabel.font = .systemFont(ofSize: 10)
|
||||
sizeLabel.textColor = AppColor.textTertiary
|
||||
|
||||
contentView.addSubview(imageView)
|
||||
imageView.addSubview(statusLabel)
|
||||
imageView.addSubview(checkImageView)
|
||||
contentView.addSubview(nameLabel)
|
||||
contentView.addSubview(sizeLabel)
|
||||
imageView.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(imageView.snp.width)
|
||||
}
|
||||
statusLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(4)
|
||||
make.height.equalTo(18)
|
||||
make.width.equalTo(48)
|
||||
}
|
||||
checkImageView.snp.makeConstraints { make in
|
||||
make.top.trailing.equalToSuperview().inset(4)
|
||||
make.size.equalTo(20)
|
||||
}
|
||||
nameLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(imageView.snp.bottom).offset(4)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
}
|
||||
sizeLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(nameLabel.snp.bottom).offset(2)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(material: TravelAlbumMaterial, selectionMode: Bool, selected: Bool) {
|
||||
let urlString = material.coverUrl.isEmpty ? material.fileUrl : material.coverUrl
|
||||
if let url = URL(string: urlString), !urlString.isEmpty {
|
||||
imageView.kf.setImage(with: url, placeholder: UIImage(systemName: "photo"))
|
||||
} else {
|
||||
imageView.image = UIImage(systemName: "photo")
|
||||
imageView.tintColor = AppColor.primary
|
||||
imageView.backgroundColor = UIColor(hex: 0xEAF2FF)
|
||||
}
|
||||
checkImageView.isHidden = !selectionMode
|
||||
checkImageView.image = UIImage(systemName: selected ? "checkmark.circle.fill" : "circle")
|
||||
checkImageView.tintColor = selected ? AppColor.primary : .white
|
||||
nameLabel.text = material.fileName
|
||||
sizeLabel.text = TravelAlbumDisplayFormatter.fileSizeText(material.fileSize)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user