完善素材管理 UI 交互并修复列表解析与时间选择。
MaterialItem 兼容 Android 缺失字段默认值,优化素材详情/列表/标签布局与上下架开关交互,修复个人空间时间选择器初始值。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -121,6 +121,22 @@ struct MaterialItem: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
|||||||
self.projectName = projectName
|
self.projectName = projectName
|
||||||
self.listingStatus = listingStatus
|
self.listingStatus = listingStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
|
||||||
|
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
|
||||||
|
coverUrl = try container.decodeIfPresent(String.self, forKey: .coverUrl) ?? ""
|
||||||
|
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
|
||||||
|
status = try container.decodeIfPresent(Int.self, forKey: .status) ?? 0
|
||||||
|
downloadCount = try container.decodeIfPresent(Int.self, forKey: .downloadCount) ?? 0
|
||||||
|
likesCount = try container.decodeIfPresent(Int.self, forKey: .likesCount) ?? 0
|
||||||
|
collectCount = try container.decodeIfPresent(Int.self, forKey: .collectCount) ?? 0
|
||||||
|
shareCount = try container.decodeIfPresent(Int.self, forKey: .shareCount) ?? 0
|
||||||
|
scenicSpotName = try container.decodeIfPresent(String.self, forKey: .scenicSpotName) ?? ""
|
||||||
|
projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? ""
|
||||||
|
listingStatus = try container.decodeIfPresent(Int.self, forKey: .listingStatus) ?? 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 素材列表响应,对齐 Android `MaterialListResponse`。
|
/// 素材列表响应,对齐 Android `MaterialListResponse`。
|
||||||
|
|||||||
@ -49,7 +49,7 @@ final class MaterialDetailViewController: BaseViewController {
|
|||||||
override func setupUI() {
|
override func setupUI() {
|
||||||
view.backgroundColor = UIColor(hex: 0xF4F4F4)
|
view.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||||
contentStack.axis = .vertical
|
contentStack.axis = .vertical
|
||||||
contentStack.spacing = 12
|
contentStack.spacing = 0
|
||||||
scrollView.backgroundColor = UIColor(hex: 0xF4F4F4)
|
scrollView.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||||
bottomBar.backgroundColor = .white
|
bottomBar.backgroundColor = .white
|
||||||
|
|
||||||
@ -60,8 +60,11 @@ final class MaterialDetailViewController: BaseViewController {
|
|||||||
scrollView.addSubview(contentStack)
|
scrollView.addSubview(contentStack)
|
||||||
contentStack.addArrangedSubview(carouselView)
|
contentStack.addArrangedSubview(carouselView)
|
||||||
contentStack.addArrangedSubview(infoSection)
|
contentStack.addArrangedSubview(infoSection)
|
||||||
|
contentStack.addArrangedSubview(makeVerticalSpacer(height: 12))
|
||||||
contentStack.addArrangedSubview(uploaderSection)
|
contentStack.addArrangedSubview(uploaderSection)
|
||||||
|
contentStack.addArrangedSubview(makeVerticalSpacer(height: 12))
|
||||||
contentStack.addArrangedSubview(reviewSection)
|
contentStack.addArrangedSubview(reviewSection)
|
||||||
|
contentStack.addArrangedSubview(makeVerticalSpacer(height: 16))
|
||||||
view.addSubview(bottomBar)
|
view.addSubview(bottomBar)
|
||||||
bottomBar.addSubview(deleteButton)
|
bottomBar.addSubview(deleteButton)
|
||||||
bottomBar.addSubview(editButton)
|
bottomBar.addSubview(editButton)
|
||||||
@ -131,6 +134,15 @@ final class MaterialDetailViewController: BaseViewController {
|
|||||||
button.layer.cornerRadius = 8
|
button.layer.cornerRadius = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func makeVerticalSpacer(height: CGFloat) -> UIView {
|
||||||
|
let spacer = UIView()
|
||||||
|
spacer.backgroundColor = .clear
|
||||||
|
spacer.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(height)
|
||||||
|
}
|
||||||
|
return spacer
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func applyViewModel() {
|
private func applyViewModel() {
|
||||||
if (viewModel.isLoading || viewModel.isDeleting) && viewModel.detail == nil {
|
if (viewModel.isLoading || viewModel.isDeleting) && viewModel.detail == nil {
|
||||||
@ -470,6 +482,7 @@ private final class MaterialDetailInfoSection: UIView {
|
|||||||
private let downloadStat = MaterialDetailStatView(iconName: "material_ic_download_count")
|
private let downloadStat = MaterialDetailStatView(iconName: "material_ic_download_count")
|
||||||
private let likeStat = MaterialDetailStatView(iconName: "sample_ic_like_count")
|
private let likeStat = MaterialDetailStatView(iconName: "sample_ic_like_count")
|
||||||
private let collectStat = MaterialDetailStatView(iconName: "sample_ic_collect_count")
|
private let collectStat = MaterialDetailStatView(iconName: "sample_ic_collect_count")
|
||||||
|
private var statsTopConstraint: Constraint?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -483,7 +496,7 @@ private final class MaterialDetailInfoSection: UIView {
|
|||||||
listingBadge.textAlignment = .center
|
listingBadge.textAlignment = .center
|
||||||
tagStack.axis = .horizontal
|
tagStack.axis = .horizontal
|
||||||
tagStack.spacing = 8
|
tagStack.spacing = 8
|
||||||
tagStack.alignment = .leading
|
tagStack.alignment = .center
|
||||||
statsStack.axis = .horizontal
|
statsStack.axis = .horizontal
|
||||||
statsStack.spacing = 16
|
statsStack.spacing = 16
|
||||||
[downloadStat, likeStat, collectStat].forEach(statsStack.addArrangedSubview)
|
[downloadStat, likeStat, collectStat].forEach(statsStack.addArrangedSubview)
|
||||||
@ -507,7 +520,7 @@ private final class MaterialDetailInfoSection: UIView {
|
|||||||
make.leading.trailing.equalToSuperview().inset(16)
|
make.leading.trailing.equalToSuperview().inset(16)
|
||||||
}
|
}
|
||||||
statsStack.snp.makeConstraints { make in
|
statsStack.snp.makeConstraints { make in
|
||||||
make.top.equalTo(tagStack.snp.bottom).offset(12)
|
statsTopConstraint = make.top.equalTo(tagStack.snp.bottom).offset(12).constraint
|
||||||
make.leading.equalToSuperview().offset(16)
|
make.leading.equalToSuperview().offset(16)
|
||||||
make.trailing.lessThanOrEqualToSuperview().inset(16)
|
make.trailing.lessThanOrEqualToSuperview().inset(16)
|
||||||
make.bottom.equalToSuperview().inset(16)
|
make.bottom.equalToSuperview().inset(16)
|
||||||
@ -527,14 +540,20 @@ private final class MaterialDetailInfoSection: UIView {
|
|||||||
listingBadge.textColor = color
|
listingBadge.textColor = color
|
||||||
listingBadge.backgroundColor = color.withAlphaComponent(0.1)
|
listingBadge.backgroundColor = color.withAlphaComponent(0.1)
|
||||||
tagStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
tagStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
(detail.materialTag ?? []).forEach { tagStack.addArrangedSubview(makeTag($0.name)) }
|
let tags = detail.materialTag ?? []
|
||||||
|
tags.forEach { tagStack.addArrangedSubview(makeTag($0.name)) }
|
||||||
|
if !tags.isEmpty {
|
||||||
|
tagStack.addArrangedSubview(UIView())
|
||||||
|
}
|
||||||
|
tagStack.isHidden = tags.isEmpty
|
||||||
|
statsTopConstraint?.update(offset: tags.isEmpty ? 0 : 12)
|
||||||
downloadStat.setCount(detail.downloadCount)
|
downloadStat.setCount(detail.downloadCount)
|
||||||
likeStat.setCount(detail.likesCount)
|
likeStat.setCount(detail.likesCount)
|
||||||
collectStat.setCount(detail.collectCount)
|
collectStat.setCount(detail.collectCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeTag(_ title: String) -> UILabel {
|
private func makeTag(_ title: String) -> UILabel {
|
||||||
let label = UILabel()
|
let label = MaterialDetailTagLabel(horizontalInset: 6)
|
||||||
label.text = title
|
label.text = title
|
||||||
label.font = .systemFont(ofSize: 12)
|
label.font = .systemFont(ofSize: 12)
|
||||||
label.textColor = AppColor.primary
|
label.textColor = AppColor.primary
|
||||||
@ -542,9 +561,10 @@ private final class MaterialDetailInfoSection: UIView {
|
|||||||
label.backgroundColor = UIColor(hex: 0xEFF6FF)
|
label.backgroundColor = UIColor(hex: 0xEFF6FF)
|
||||||
label.layer.cornerRadius = 4
|
label.layer.cornerRadius = 4
|
||||||
label.clipsToBounds = true
|
label.clipsToBounds = true
|
||||||
|
label.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
label.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
label.snp.makeConstraints { make in
|
label.snp.makeConstraints { make in
|
||||||
make.height.equalTo(22)
|
make.height.equalTo(22)
|
||||||
make.width.greaterThanOrEqualTo(40)
|
|
||||||
}
|
}
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
@ -632,7 +652,7 @@ private final class MaterialDetailUploaderSection: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func makeTag(_ title: String) -> UILabel {
|
private func makeTag(_ title: String) -> UILabel {
|
||||||
let label = UILabel()
|
let label = MaterialDetailTagLabel(horizontalInset: 6)
|
||||||
label.text = title
|
label.text = title
|
||||||
label.textColor = .white
|
label.textColor = .white
|
||||||
label.font = .systemFont(ofSize: 8)
|
label.font = .systemFont(ofSize: 8)
|
||||||
@ -642,12 +662,35 @@ private final class MaterialDetailUploaderSection: UIView {
|
|||||||
label.clipsToBounds = true
|
label.clipsToBounds = true
|
||||||
label.snp.makeConstraints { make in
|
label.snp.makeConstraints { make in
|
||||||
make.height.equalTo(16)
|
make.height.equalTo(16)
|
||||||
make.width.greaterThanOrEqualTo(44)
|
|
||||||
}
|
}
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 为详情页标签文本提供固定水平内边距,使背景宽度随内容自适应。
|
||||||
|
private final class MaterialDetailTagLabel: UILabel {
|
||||||
|
private let horizontalInset: CGFloat
|
||||||
|
|
||||||
|
init(horizontalInset: CGFloat) {
|
||||||
|
self.horizontalInset = horizontalInset
|
||||||
|
super.init(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func drawText(in rect: CGRect) {
|
||||||
|
super.drawText(in: rect.insetBy(dx: horizontalInset, dy: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
let size = super.intrinsicContentSize
|
||||||
|
return CGSize(width: size.width + horizontalInset * 2, height: size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 素材审核信息区。
|
/// 素材审核信息区。
|
||||||
/// 素材详情的审核信息区块。
|
/// 素材详情的审核信息区块。
|
||||||
private final class MaterialDetailReviewSection: UIView {
|
private final class MaterialDetailReviewSection: UIView {
|
||||||
|
|||||||
@ -972,7 +972,7 @@ private final class MaterialTagEditorView: UIView {
|
|||||||
stack.spacing = 8
|
stack.spacing = 8
|
||||||
tagsStack.axis = .horizontal
|
tagsStack.axis = .horizontal
|
||||||
tagsStack.spacing = 8
|
tagsStack.spacing = 8
|
||||||
tagsStack.alignment = .leading
|
tagsStack.alignment = .center
|
||||||
inputRow.axis = .horizontal
|
inputRow.axis = .horizontal
|
||||||
inputRow.spacing = 8
|
inputRow.spacing = 8
|
||||||
textField.font = .systemFont(ofSize: 14)
|
textField.font = .systemFont(ofSize: 14)
|
||||||
@ -1027,6 +1027,8 @@ private final class MaterialTagEditorView: UIView {
|
|||||||
button.backgroundColor = UIColor(hex: 0xEFF6FF)
|
button.backgroundColor = UIColor(hex: 0xEFF6FF)
|
||||||
button.layer.cornerRadius = 4
|
button.layer.cornerRadius = 4
|
||||||
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
|
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
|
||||||
|
button.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
button.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
button.snp.makeConstraints { make in
|
button.snp.makeConstraints { make in
|
||||||
make.height.equalTo(28)
|
make.height.equalTo(28)
|
||||||
}
|
}
|
||||||
@ -1035,6 +1037,10 @@ private final class MaterialTagEditorView: UIView {
|
|||||||
}, for: .touchUpInside)
|
}, for: .touchUpInside)
|
||||||
tagsStack.addArrangedSubview(button)
|
tagsStack.addArrangedSubview(button)
|
||||||
}
|
}
|
||||||
|
if !tags.isEmpty {
|
||||||
|
// Android Row 中标签按内容宽度排列;尾部弹性占位吸收剩余空间。
|
||||||
|
tagsStack.addArrangedSubview(UIView())
|
||||||
|
}
|
||||||
tagsStack.isHidden = tags.isEmpty
|
tagsStack.isHidden = tags.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -229,7 +229,7 @@ final class MaterialListViewController: BaseViewController {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func applyViewModel() {
|
private func applyViewModel() {
|
||||||
filterTitleLabel.text = "筛选"
|
filterTitleLabel.text = viewModel.filterStatus.title
|
||||||
filterOptionButtons.forEach { status, button in
|
filterOptionButtons.forEach { status, button in
|
||||||
let selected = status == viewModel.filterStatus
|
let selected = status == viewModel.filterStatus
|
||||||
button.setTitleColor(selected ? AppColor.primary : AppColor.textPrimary, for: .normal)
|
button.setTitleColor(selected ? AppColor.primary : AppColor.textPrimary, for: .normal)
|
||||||
@ -436,6 +436,7 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
private let cardView = UIView()
|
private let cardView = UIView()
|
||||||
private let coverImageView = UIImageView()
|
private let coverImageView = UIImageView()
|
||||||
private let placeholderLabel = UILabel()
|
private let placeholderLabel = UILabel()
|
||||||
|
private let detailContentView = UIView()
|
||||||
private let titleLabel = UILabel()
|
private let titleLabel = UILabel()
|
||||||
private let timeLabel = UILabel()
|
private let timeLabel = UILabel()
|
||||||
private let statusBadge = MaterialStatusBadgeView()
|
private let statusBadge = MaterialStatusBadgeView()
|
||||||
@ -445,6 +446,7 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
private let likeStatView = MaterialStatView(iconName: "sample_ic_like_count")
|
private let likeStatView = MaterialStatView(iconName: "sample_ic_like_count")
|
||||||
private let collectStatView = MaterialStatView(iconName: "sample_ic_collect_count")
|
private let collectStatView = MaterialStatView(iconName: "sample_ic_collect_count")
|
||||||
private var isApplying = false
|
private var isApplying = false
|
||||||
|
private var confirmedListingState = false
|
||||||
|
|
||||||
var onToggle: ((Bool) -> Void)?
|
var onToggle: ((Bool) -> Void)?
|
||||||
|
|
||||||
@ -460,10 +462,11 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
|
|
||||||
func apply(item: MaterialItem) {
|
func apply(item: MaterialItem) {
|
||||||
isApplying = true
|
isApplying = true
|
||||||
|
confirmedListingState = item.listingStatus == 1
|
||||||
titleLabel.text = item.name
|
titleLabel.text = item.name
|
||||||
timeLabel.text = item.createdAt
|
timeLabel.text = item.createdAt
|
||||||
statusBadge.apply(status: item.status)
|
statusBadge.apply(status: item.status)
|
||||||
listingSwitch.setOn(item.listingStatus == 1, animated: false)
|
listingSwitch.setOn(confirmedListingState, animated: false)
|
||||||
downloadStatView.setCount(item.downloadCount)
|
downloadStatView.setCount(item.downloadCount)
|
||||||
likeStatView.setCount(item.likesCount)
|
likeStatView.setCount(item.likesCount)
|
||||||
collectStatView.setCount(item.collectCount)
|
collectStatView.setCount(item.collectCount)
|
||||||
@ -515,11 +518,12 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
contentView.addSubview(cardView)
|
contentView.addSubview(cardView)
|
||||||
cardView.addSubview(coverImageView)
|
cardView.addSubview(coverImageView)
|
||||||
coverImageView.addSubview(placeholderLabel)
|
coverImageView.addSubview(placeholderLabel)
|
||||||
cardView.addSubview(titleLabel)
|
cardView.addSubview(detailContentView)
|
||||||
cardView.addSubview(timeLabel)
|
detailContentView.addSubview(titleLabel)
|
||||||
cardView.addSubview(statusBadge)
|
detailContentView.addSubview(timeLabel)
|
||||||
cardView.addSubview(listingSwitch)
|
detailContentView.addSubview(statusBadge)
|
||||||
cardView.addSubview(statsStack)
|
detailContentView.addSubview(listingSwitch)
|
||||||
|
detailContentView.addSubview(statsStack)
|
||||||
[downloadStatView, likeStatView, collectStatView].forEach(statsStack.addArrangedSubview)
|
[downloadStatView, likeStatView, collectStatView].forEach(statsStack.addArrangedSubview)
|
||||||
|
|
||||||
cardView.snp.makeConstraints { make in
|
cardView.snp.makeConstraints { make in
|
||||||
@ -535,13 +539,18 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
make.leading.trailing.equalToSuperview().inset(8)
|
make.leading.trailing.equalToSuperview().inset(8)
|
||||||
}
|
}
|
||||||
titleLabel.snp.makeConstraints { make in
|
detailContentView.snp.makeConstraints { make in
|
||||||
make.top.equalToSuperview().offset(16)
|
|
||||||
make.leading.equalTo(coverImageView.snp.trailing).offset(12)
|
make.leading.equalTo(coverImageView.snp.trailing).offset(12)
|
||||||
make.trailing.equalToSuperview().inset(12)
|
make.trailing.equalToSuperview().inset(12)
|
||||||
|
make.centerY.equalTo(coverImageView)
|
||||||
|
make.top.greaterThanOrEqualToSuperview().inset(12)
|
||||||
|
make.bottom.lessThanOrEqualToSuperview().inset(12)
|
||||||
|
}
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.top.leading.trailing.equalToSuperview()
|
||||||
}
|
}
|
||||||
timeLabel.snp.makeConstraints { make in
|
timeLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(titleLabel.snp.bottom).offset(4)
|
make.top.equalTo(titleLabel.snp.bottom).offset(2)
|
||||||
make.leading.trailing.equalTo(titleLabel)
|
make.leading.trailing.equalTo(titleLabel)
|
||||||
}
|
}
|
||||||
statusBadge.snp.makeConstraints { make in
|
statusBadge.snp.makeConstraints { make in
|
||||||
@ -551,20 +560,22 @@ private final class MaterialListCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
listingSwitch.snp.makeConstraints { make in
|
listingSwitch.snp.makeConstraints { make in
|
||||||
make.centerY.equalTo(statusBadge)
|
make.centerY.equalTo(statusBadge)
|
||||||
make.trailing.equalToSuperview().inset(12)
|
make.trailing.equalToSuperview()
|
||||||
make.leading.greaterThanOrEqualTo(statusBadge.snp.trailing).offset(8)
|
make.leading.greaterThanOrEqualTo(statusBadge.snp.trailing).offset(8)
|
||||||
}
|
}
|
||||||
statsStack.snp.makeConstraints { make in
|
statsStack.snp.makeConstraints { make in
|
||||||
make.top.equalTo(statusBadge.snp.bottom).offset(8)
|
make.top.equalTo(statusBadge.snp.bottom).offset(8)
|
||||||
make.leading.equalTo(titleLabel)
|
make.leading.equalToSuperview()
|
||||||
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
make.trailing.lessThanOrEqualToSuperview()
|
||||||
make.bottom.lessThanOrEqualToSuperview().inset(12)
|
make.bottom.equalToSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func switchChanged() {
|
@objc private func switchChanged() {
|
||||||
guard !isApplying else { return }
|
guard !isApplying else { return }
|
||||||
onToggle?(listingSwitch.isOn)
|
let requestedState = listingSwitch.isOn
|
||||||
|
listingSwitch.setOn(confirmedListingState, animated: true)
|
||||||
|
onToggle?(requestedState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -607,14 +607,13 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
|
|||||||
|
|
||||||
private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
|
private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
|
||||||
let dialog = ProfileTimePickerDialogViewController(
|
let dialog = ProfileTimePickerDialogViewController(
|
||||||
initialDate: date(fromTime: "00:00"),
|
initialDate: date(fromTime: initialTime ?? "00:00"),
|
||||||
onConfirm: { [weak self] date in
|
onConfirm: { [weak self] date in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
onConfirm(timeString(from: date))
|
onConfirm(timeString(from: date))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
present(dialog, animated: false)
|
present(dialog, animated: false)
|
||||||
_ = initialTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func date(fromTime value: String) -> Date {
|
private func date(fromTime value: String) -> Date {
|
||||||
@ -976,7 +975,7 @@ final class ProfileAddScheduleViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
|
private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
|
||||||
let date = Self.date(from: "00:00")
|
let date = Self.date(from: initialTime)
|
||||||
let dialog = ProfileTimePickerDialogViewController(initialDate: date) { selectedDate in
|
let dialog = ProfileTimePickerDialogViewController(initialDate: date) { selectedDate in
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
@ -984,7 +983,6 @@ final class ProfileAddScheduleViewController: BaseViewController {
|
|||||||
onConfirm(formatter.string(from: selectedDate))
|
onConfirm(formatter.string(from: selectedDate))
|
||||||
}
|
}
|
||||||
present(dialog, animated: false)
|
present(dialog, animated: false)
|
||||||
_ = initialTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func startTapped() {
|
@objc private func startTapped() {
|
||||||
@ -1063,7 +1061,9 @@ final class ProfileAddScheduleViewController: BaseViewController {
|
|||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
formatter.dateFormat = "HH:mm"
|
formatter.dateFormat = "HH:mm"
|
||||||
return formatter.date(from: time ?? "00:00") ?? Date()
|
return formatter.date(from: time ?? "")
|
||||||
|
?? formatter.date(from: "00:00")
|
||||||
|
?? Date(timeIntervalSince1970: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1265,6 +1265,9 @@ private final class ProfileTagWrapView: UIView {
|
|||||||
case .device:
|
case .device:
|
||||||
button.backgroundColor = AppColor.inputBackground
|
button.backgroundColor = AppColor.inputBackground
|
||||||
button.setTitleColor(UIColor(hex: 0x4B5563), for: .normal)
|
button.setTitleColor(UIColor(hex: 0x4B5563), for: .normal)
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(28)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if removable {
|
if removable {
|
||||||
button.addAction(UIAction { [weak self] _ in
|
button.addAction(UIAction { [weak self] _ in
|
||||||
|
|||||||
@ -38,6 +38,20 @@ final class MaterialManagementAPITests: XCTestCase {
|
|||||||
XCTAssertEqual(response.order?.totalNum, 0)
|
XCTAssertEqual(response.order?.totalNum, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testListDecodesAndroidDefaultsWhenNamesAreMissing() async throws {
|
||||||
|
let item = #"{"id":56,"name":"iOS视频素材测试","cover_url":"https://cdn/cover.jpg","created_at":"2026-07-13 11:36:30","status":0,"download_count":0,"likes_count":0,"collect_count":0,"share_count":0,"listing_status":0,"type":"2"}"#
|
||||||
|
let session = MockURLSession(responses: [envelopeJSON(#"{"total":1,"list":[\#(item)],"order":{"avg_change":1,"avg_order_amount":null,"refund_total":null,"total_num":0}}"#)])
|
||||||
|
let api = MaterialManagementAPI(client: APIClient(environment: .testing, session: session))
|
||||||
|
|
||||||
|
let response = try await api.list(status: 4, keyword: nil, page: 1, pageSize: 10)
|
||||||
|
|
||||||
|
let material = try XCTUnwrap(response.list.first)
|
||||||
|
XCTAssertEqual(material.id, 56)
|
||||||
|
XCTAssertEqual(material.name, "iOS视频素材测试")
|
||||||
|
XCTAssertEqual(material.scenicSpotName, "")
|
||||||
|
XCTAssertEqual(material.projectName, "")
|
||||||
|
}
|
||||||
|
|
||||||
func testDetailOperationDeleteAndTagUseAndroidPaths() async throws {
|
func testDetailOperationDeleteAndTagUseAndroidPaths() async throws {
|
||||||
let detail = envelopeJSON(#"{"id":8,"status":1,"listing_status":1}"#)
|
let detail = envelopeJSON(#"{"id":8,"status":1,"listing_status":1}"#)
|
||||||
let empty = envelopeJSON("{}")
|
let empty = envelopeJSON("{}")
|
||||||
|
|||||||
Reference in New Issue
Block a user