312 lines
13 KiB
Swift
312 lines
13 KiB
Swift
//
|
||
// WiredCameraTransferViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 有线相机传输页,仅同步 Android 断开空态 UI,不接入 OTG 或上传业务。
|
||
final class WiredCameraTransferViewController: BaseViewController {
|
||
private let viewModel: WiredCameraTransferViewModel
|
||
|
||
private let headerView = UIView()
|
||
private let backButton = UIButton(type: .system)
|
||
private let titleLabel = UILabel()
|
||
private let phoneLabel = UILabel()
|
||
private let statusCard = UIView()
|
||
private let storagePanel = UIView()
|
||
private let storageTitleLabel = UILabel()
|
||
private let storageValueLabel = UILabel()
|
||
private let statusLabel = UILabel()
|
||
private let refreshButton = UIButton(type: .system)
|
||
private let helpLabel = UILabel()
|
||
private let chipsStack = UIStackView()
|
||
private let statsCard = UIStackView()
|
||
private let emptyLabel = UILabel()
|
||
private let bottomBar = UIView()
|
||
private let batchButton = UIButton(type: .system)
|
||
private let specifyButton = UIButton(type: .system)
|
||
|
||
init(viewModel: WiredCameraTransferViewModel) {
|
||
self.viewModel = viewModel
|
||
super.init(nibName: nil, bundle: nil)
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func setupNavigationBar() {
|
||
navigationController?.setNavigationBarHidden(true, animated: false)
|
||
}
|
||
|
||
override func viewWillDisappear(_ animated: Bool) {
|
||
super.viewWillDisappear(animated)
|
||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||
}
|
||
|
||
override func setupUI() {
|
||
view.backgroundColor = AppColor.pageBackground
|
||
headerView.backgroundColor = AppColor.primary
|
||
backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal)
|
||
backButton.tintColor = .white
|
||
titleLabel.text = viewModel.albumTitle
|
||
titleLabel.font = .systemFont(ofSize: 20, weight: .bold)
|
||
titleLabel.textColor = .white
|
||
phoneLabel.text = viewModel.headerPhone
|
||
phoneLabel.font = .systemFont(ofSize: 13)
|
||
phoneLabel.textColor = UIColor.white.withAlphaComponent(0.92)
|
||
|
||
statusCard.backgroundColor = .white
|
||
statusCard.layer.cornerRadius = 12
|
||
statusCard.layer.shadowColor = UIColor.black.withAlphaComponent(0.10).cgColor
|
||
statusCard.layer.shadowOpacity = 1
|
||
statusCard.layer.shadowRadius = 8
|
||
statusCard.layer.shadowOffset = CGSize(width: 0, height: 3)
|
||
|
||
storagePanel.layer.cornerRadius = 12
|
||
storagePanel.layer.borderColor = AppColor.border.cgColor
|
||
storagePanel.layer.borderWidth = 3
|
||
storageTitleLabel.text = viewModel.deviceModelName
|
||
storageTitleLabel.font = .systemFont(ofSize: 11, weight: .medium)
|
||
storageTitleLabel.textColor = AppColor.textPrimary
|
||
storageTitleLabel.textAlignment = .center
|
||
storageValueLabel.text = viewModel.availableStorageText
|
||
storageValueLabel.font = .systemFont(ofSize: 10)
|
||
storageValueLabel.textColor = AppColor.primary
|
||
storageValueLabel.textAlignment = .center
|
||
|
||
statusLabel.text = viewModel.cameraStatusText
|
||
statusLabel.font = .systemFont(ofSize: 11, weight: .medium)
|
||
statusLabel.textColor = AppColor.danger
|
||
statusLabel.backgroundColor = AppColor.danger.withAlphaComponent(0.10)
|
||
statusLabel.layer.cornerRadius = 4
|
||
statusLabel.clipsToBounds = true
|
||
statusLabel.textAlignment = .center
|
||
refreshButton.setTitle(viewModel.actionButtonText, for: .normal)
|
||
refreshButton.setTitleColor(.white, for: .normal)
|
||
refreshButton.titleLabel?.font = .systemFont(ofSize: 12)
|
||
refreshButton.backgroundColor = AppColor.danger
|
||
refreshButton.layer.cornerRadius = 8
|
||
helpLabel.attributedText = helpText()
|
||
helpLabel.font = .systemFont(ofSize: 10)
|
||
helpLabel.numberOfLines = 2
|
||
|
||
chipsStack.axis = .horizontal
|
||
chipsStack.spacing = 6
|
||
[viewModel.retouchOption, viewModel.photoFormatOption, viewModel.transferModeOption].forEach {
|
||
chipsStack.addArrangedSubview(makeChip($0))
|
||
}
|
||
|
||
statsCard.axis = .horizontal
|
||
statsCard.distribution = .fillEqually
|
||
["全部", "已上传", "失败"].enumerated().forEach { index, title in
|
||
statsCard.addArrangedSubview(makeStat(title: title, count: viewModel.tabCounts[index], selected: index == 0, danger: index == 2))
|
||
}
|
||
|
||
emptyLabel.text = "暂无照片"
|
||
emptyLabel.font = .systemFont(ofSize: 14)
|
||
emptyLabel.textColor = AppColor.textTertiary
|
||
emptyLabel.textAlignment = .center
|
||
|
||
bottomBar.backgroundColor = .white
|
||
configureBottomButton(batchButton, title: "批量上传", filled: true)
|
||
configureBottomButton(specifyButton, title: "指定上传", filled: false)
|
||
|
||
view.addSubview(headerView)
|
||
headerView.addSubview(backButton)
|
||
headerView.addSubview(titleLabel)
|
||
headerView.addSubview(phoneLabel)
|
||
view.addSubview(statusCard)
|
||
statusCard.addSubview(storagePanel)
|
||
storagePanel.addSubview(storageTitleLabel)
|
||
storagePanel.addSubview(storageValueLabel)
|
||
statusCard.addSubview(statusLabel)
|
||
statusCard.addSubview(refreshButton)
|
||
statusCard.addSubview(helpLabel)
|
||
statusCard.addSubview(chipsStack)
|
||
statusCard.addSubview(statsCard)
|
||
view.addSubview(emptyLabel)
|
||
view.addSubview(bottomBar)
|
||
bottomBar.addSubview(batchButton)
|
||
bottomBar.addSubview(specifyButton)
|
||
}
|
||
|
||
override func setupConstraints() {
|
||
headerView.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview()
|
||
make.height.equalTo(118)
|
||
}
|
||
backButton.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().offset(8)
|
||
make.bottom.equalToSuperview().offset(-28)
|
||
make.size.equalTo(44)
|
||
}
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.leading.equalTo(backButton.snp.trailing).offset(4)
|
||
make.trailing.equalToSuperview().offset(-16)
|
||
make.top.equalTo(backButton).offset(4)
|
||
}
|
||
phoneLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(titleLabel.snp.bottom).offset(2)
|
||
make.leading.trailing.equalTo(titleLabel)
|
||
}
|
||
statusCard.snp.makeConstraints { make in
|
||
make.top.equalTo(headerView.snp.bottom).offset(-10)
|
||
make.leading.trailing.equalToSuperview().inset(16)
|
||
}
|
||
storagePanel.snp.makeConstraints { make in
|
||
make.top.leading.equalToSuperview().offset(14)
|
||
make.width.greaterThanOrEqualTo(76)
|
||
make.height.equalTo(76)
|
||
}
|
||
storageTitleLabel.snp.makeConstraints { make in
|
||
make.top.equalToSuperview().offset(10)
|
||
make.leading.trailing.equalToSuperview().inset(8)
|
||
}
|
||
storageValueLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(storageTitleLabel.snp.bottom).offset(6)
|
||
make.leading.trailing.equalTo(storageTitleLabel)
|
||
}
|
||
statusLabel.snp.makeConstraints { make in
|
||
make.top.equalToSuperview().offset(16)
|
||
make.leading.equalTo(storagePanel.snp.trailing).offset(10)
|
||
make.height.equalTo(22)
|
||
make.width.greaterThanOrEqualTo(84)
|
||
}
|
||
refreshButton.snp.makeConstraints { make in
|
||
make.centerY.equalTo(statusLabel)
|
||
make.trailing.equalToSuperview().offset(-14)
|
||
make.height.equalTo(34)
|
||
make.width.greaterThanOrEqualTo(72)
|
||
}
|
||
helpLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(statusLabel.snp.bottom).offset(4)
|
||
make.leading.equalTo(statusLabel)
|
||
make.trailing.equalTo(refreshButton)
|
||
}
|
||
chipsStack.snp.makeConstraints { make in
|
||
make.top.equalTo(storagePanel.snp.bottom).offset(12)
|
||
make.leading.trailing.equalToSuperview().inset(14)
|
||
make.height.equalTo(34)
|
||
}
|
||
statsCard.snp.makeConstraints { make in
|
||
make.top.equalTo(chipsStack.snp.bottom).offset(14)
|
||
make.leading.trailing.bottom.equalToSuperview()
|
||
make.height.equalTo(68)
|
||
}
|
||
bottomBar.snp.makeConstraints { make in
|
||
make.leading.trailing.bottom.equalToSuperview()
|
||
}
|
||
batchButton.snp.makeConstraints { make in
|
||
make.top.equalToSuperview().offset(12)
|
||
make.leading.equalToSuperview().offset(16)
|
||
make.height.equalTo(44)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
||
}
|
||
specifyButton.snp.makeConstraints { make in
|
||
make.top.height.width.equalTo(batchButton)
|
||
make.leading.equalTo(batchButton.snp.trailing).offset(12)
|
||
make.trailing.equalToSuperview().offset(-16)
|
||
}
|
||
emptyLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(statusCard.snp.bottom)
|
||
make.leading.trailing.equalToSuperview()
|
||
make.bottom.equalTo(bottomBar.snp.top)
|
||
}
|
||
}
|
||
|
||
override func bindActions() {
|
||
viewModel.onShowMessage = { [weak self] message in
|
||
Task { @MainActor in self?.showToast(message) }
|
||
}
|
||
backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
|
||
refreshButton.addTarget(self, action: #selector(uiOnlyTapped), for: .touchUpInside)
|
||
batchButton.addTarget(self, action: #selector(uiOnlyTapped), for: .touchUpInside)
|
||
specifyButton.addTarget(self, action: #selector(uiOnlyTapped), for: .touchUpInside)
|
||
}
|
||
|
||
private func makeChip(_ text: String) -> UIView {
|
||
let label = UILabel()
|
||
label.text = text
|
||
label.font = .systemFont(ofSize: 11)
|
||
label.textColor = AppColor.textTertiary
|
||
label.backgroundColor = AppColor.pageBackground
|
||
label.layer.cornerRadius = 8
|
||
label.layer.borderWidth = 1
|
||
label.layer.borderColor = AppColor.border.cgColor
|
||
label.clipsToBounds = true
|
||
label.textAlignment = .center
|
||
return label
|
||
}
|
||
|
||
private func makeStat(title: String, count: Int, selected: Bool, danger: Bool) -> UIView {
|
||
let container = UIView()
|
||
let countLabel = UILabel()
|
||
let titleLabel = UILabel()
|
||
let underline = UIView()
|
||
countLabel.text = "\(count)"
|
||
countLabel.font = .systemFont(ofSize: 18, weight: .semibold)
|
||
countLabel.textColor = selected ? AppColor.primary : (danger ? AppColor.danger : AppColor.textPrimary)
|
||
countLabel.textAlignment = .center
|
||
titleLabel.text = title
|
||
titleLabel.font = .systemFont(ofSize: 13)
|
||
titleLabel.textColor = selected ? AppColor.primary : AppColor.textSecondary
|
||
titleLabel.textAlignment = .center
|
||
underline.backgroundColor = selected ? AppColor.primary : .clear
|
||
underline.layer.cornerRadius = 1
|
||
container.addSubview(countLabel)
|
||
container.addSubview(titleLabel)
|
||
container.addSubview(underline)
|
||
countLabel.snp.makeConstraints { make in
|
||
make.top.equalToSuperview().offset(8)
|
||
make.leading.trailing.equalToSuperview()
|
||
}
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(countLabel.snp.bottom).offset(2)
|
||
make.leading.trailing.equalToSuperview()
|
||
}
|
||
underline.snp.makeConstraints { make in
|
||
make.top.equalTo(titleLabel.snp.bottom).offset(4)
|
||
make.centerX.equalToSuperview()
|
||
make.width.equalTo(24)
|
||
make.height.equalTo(2)
|
||
}
|
||
return container
|
||
}
|
||
|
||
private func configureBottomButton(_ button: UIButton, title: String, filled: Bool) {
|
||
button.setTitle(title, for: .normal)
|
||
button.titleLabel?.font = .systemFont(ofSize: 15)
|
||
button.layer.cornerRadius = 10
|
||
if filled {
|
||
button.backgroundColor = AppColor.primary
|
||
button.setTitleColor(.white, for: .normal)
|
||
} else {
|
||
button.backgroundColor = .white
|
||
button.setTitleColor(AppColor.primary, for: .normal)
|
||
button.layer.borderWidth = 1
|
||
button.layer.borderColor = AppColor.primary.cgColor
|
||
}
|
||
}
|
||
|
||
private func helpText() -> NSAttributedString {
|
||
let text = NSMutableAttributedString(
|
||
string: "未连接,查看",
|
||
attributes: [.foregroundColor: AppColor.textSecondary]
|
||
)
|
||
text.append(NSAttributedString(string: "《帮助文档》", attributes: [.foregroundColor: AppColor.primary]))
|
||
return text
|
||
}
|
||
|
||
@objc private func backTapped() {
|
||
navigationController?.popViewController(animated: true)
|
||
}
|
||
|
||
@objc private func uiOnlyTapped() {
|
||
viewModel.showUIOnlyMessage()
|
||
}
|
||
}
|