完善账号切换展示、首页菜单图标与有线传输设置 chip。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -4,6 +4,14 @@
|
|||||||
"filename" : "home_menu_space.svg",
|
"filename" : "home_menu_space.svg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
@ -4,6 +4,14 @@
|
|||||||
"filename" : "profile_avatar_default.svg",
|
"filename" : "profile_avatar_default.svg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
@ -4,6 +4,14 @@
|
|||||||
"filename" : "profile_edit.svg",
|
"filename" : "profile_edit.svg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
@ -118,6 +118,7 @@ struct AccountSwitchAccount: Identifiable, Hashable {
|
|||||||
let title: String
|
let title: String
|
||||||
let subtitle: String
|
let subtitle: String
|
||||||
let phone: String
|
let phone: String
|
||||||
|
let realName: String
|
||||||
let avatar: String
|
let avatar: String
|
||||||
let scenicName: String
|
let scenicName: String
|
||||||
let storeId: Int?
|
let storeId: Int?
|
||||||
@ -201,6 +202,7 @@ struct V9ScenicUser: Decodable, Equatable {
|
|||||||
let phone: String
|
let phone: String
|
||||||
let scenicId: Int
|
let scenicId: Int
|
||||||
let scenicName: String
|
let scenicName: String
|
||||||
|
let roleName: String
|
||||||
let appRoleCode: String
|
let appRoleCode: String
|
||||||
let appRoleName: String
|
let appRoleName: String
|
||||||
let isCurrent: Bool
|
let isCurrent: Bool
|
||||||
@ -218,8 +220,9 @@ struct V9ScenicUser: Decodable, Equatable {
|
|||||||
accountType: accountType.nonEmpty ?? Self.accountTypeValue,
|
accountType: accountType.nonEmpty ?? Self.accountTypeValue,
|
||||||
businessUserId: businessUserId,
|
businessUserId: businessUserId,
|
||||||
title: scenicName.nonEmpty ?? nickname.nonEmpty ?? realName.nonEmpty ?? "景区账号",
|
title: scenicName.nonEmpty ?? nickname.nonEmpty ?? realName.nonEmpty ?? "景区账号",
|
||||||
subtitle: username,
|
subtitle: joinAccountSubtitle(scenicName, roleName),
|
||||||
phone: phone,
|
phone: phone,
|
||||||
|
realName: realName,
|
||||||
avatar: "",
|
avatar: "",
|
||||||
scenicName: scenicName,
|
scenicName: scenicName,
|
||||||
storeId: nil,
|
storeId: nil,
|
||||||
@ -242,6 +245,7 @@ struct V9ScenicUser: Decodable, Equatable {
|
|||||||
case phone
|
case phone
|
||||||
case scenicId = "scenic_id"
|
case scenicId = "scenic_id"
|
||||||
case scenicName = "scenic_name"
|
case scenicName = "scenic_name"
|
||||||
|
case roleName = "role_name"
|
||||||
case appRoleCode = "app_role_code"
|
case appRoleCode = "app_role_code"
|
||||||
case appRoleName = "app_role_name"
|
case appRoleName = "app_role_name"
|
||||||
case isCurrent = "is_current"
|
case isCurrent = "is_current"
|
||||||
@ -262,6 +266,7 @@ struct V9ScenicUser: Decodable, Equatable {
|
|||||||
phone = try container.decodeLossyString(forKey: .phone)
|
phone = try container.decodeLossyString(forKey: .phone)
|
||||||
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
|
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
|
||||||
scenicName = try container.decodeLossyString(forKey: .scenicName)
|
scenicName = try container.decodeLossyString(forKey: .scenicName)
|
||||||
|
roleName = try container.decodeLossyString(forKey: .roleName)
|
||||||
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
|
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
|
||||||
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
|
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
|
||||||
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
|
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
|
||||||
@ -285,6 +290,7 @@ struct V9StoreUser: Decodable, Equatable {
|
|||||||
let scenicName: String
|
let scenicName: String
|
||||||
let storeId: Int
|
let storeId: Int
|
||||||
let storeName: String
|
let storeName: String
|
||||||
|
let roleName: String
|
||||||
let appRoleCode: String
|
let appRoleCode: String
|
||||||
let appRoleName: String
|
let appRoleName: String
|
||||||
let isCurrent: Bool
|
let isCurrent: Bool
|
||||||
@ -302,8 +308,9 @@ struct V9StoreUser: Decodable, Equatable {
|
|||||||
accountType: accountType.nonEmpty ?? Self.accountTypeValue,
|
accountType: accountType.nonEmpty ?? Self.accountTypeValue,
|
||||||
businessUserId: businessUserId,
|
businessUserId: businessUserId,
|
||||||
title: storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? "门店账号",
|
title: storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? "门店账号",
|
||||||
subtitle: userName.nonEmpty ?? username,
|
subtitle: joinAccountSubtitle(scenicName, roleName),
|
||||||
phone: phone,
|
phone: phone,
|
||||||
|
realName: realName,
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
scenicName: scenicName,
|
scenicName: scenicName,
|
||||||
storeId: storeId > 0 ? storeId : nil,
|
storeId: storeId > 0 ? storeId : nil,
|
||||||
@ -327,6 +334,7 @@ struct V9StoreUser: Decodable, Equatable {
|
|||||||
case scenicName = "scenic_name"
|
case scenicName = "scenic_name"
|
||||||
case storeId = "store_id"
|
case storeId = "store_id"
|
||||||
case storeName = "store_name"
|
case storeName = "store_name"
|
||||||
|
case roleName = "role_name"
|
||||||
case appRoleCode = "app_role_code"
|
case appRoleCode = "app_role_code"
|
||||||
case appRoleName = "app_role_name"
|
case appRoleName = "app_role_name"
|
||||||
case isCurrent = "is_current"
|
case isCurrent = "is_current"
|
||||||
@ -347,6 +355,7 @@ struct V9StoreUser: Decodable, Equatable {
|
|||||||
scenicName = try container.decodeLossyString(forKey: .scenicName)
|
scenicName = try container.decodeLossyString(forKey: .scenicName)
|
||||||
storeId = try container.decodeLossyInt(forKey: .storeId) ?? 0
|
storeId = try container.decodeLossyInt(forKey: .storeId) ?? 0
|
||||||
storeName = try container.decodeLossyString(forKey: .storeName)
|
storeName = try container.decodeLossyString(forKey: .storeName)
|
||||||
|
roleName = try container.decodeLossyString(forKey: .roleName)
|
||||||
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
|
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
|
||||||
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
|
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
|
||||||
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
|
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
|
||||||
@ -405,6 +414,14 @@ private extension KeyedDecodingContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 将非空字段用小圆点拼接为账号副标题。
|
||||||
|
private func joinAccountSubtitle(_ values: String...) -> String {
|
||||||
|
values
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
.joined(separator: " · ")
|
||||||
|
}
|
||||||
|
|
||||||
private extension String {
|
private extension String {
|
||||||
var nonEmpty: String? {
|
var nonEmpty: String? {
|
||||||
let text = trimmingCharacters(in: .whitespacesAndNewlines)
|
let text = trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|||||||
21
suixinkan/Features/Home/Services/HomeMenuIconFactory.swift
Normal file
21
suixinkan/Features/Home/Services/HomeMenuIconFactory.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// HomeMenuIconFactory.swift
|
||||||
|
// suixinkan
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// 首页 / 全部功能菜单图标加载,统一 SF Symbol 与资源图的视觉尺寸。
|
||||||
|
enum HomeMenuIconFactory {
|
||||||
|
|
||||||
|
/// 与菜单图标槽位(约 24–26pt)对齐的系统图标配置。
|
||||||
|
static let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)
|
||||||
|
|
||||||
|
/// 优先加载 Assets 资源图;否则按统一配置加载 SF Symbol。
|
||||||
|
static func image(named iconName: String) -> UIImage? {
|
||||||
|
if let asset = UIImage(named: iconName) {
|
||||||
|
return asset
|
||||||
|
}
|
||||||
|
return UIImage(systemName: iconName, withConfiguration: symbolConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,10 +42,11 @@ final class AllFunctionMenuCell: UICollectionViewCell {
|
|||||||
|
|
||||||
/// 更新菜单内容、操作状态及整张卡片的无障碍语义。
|
/// 更新菜单内容、操作状态及整张卡片的无障碍语义。
|
||||||
func apply(menu: HomeMenuItem, actionStyle: ActionStyle) {
|
func apply(menu: HomeMenuItem, actionStyle: ActionStyle) {
|
||||||
iconView.image = UIImage(named: menu.iconName) ?? UIImage(systemName: menu.iconName)
|
iconView.image = HomeMenuIconFactory.image(named: menu.iconName)
|
||||||
titleLabel.text = menu.title
|
titleLabel.text = menu.title
|
||||||
accessibilityLabel = menu.title
|
accessibilityLabel = menu.title
|
||||||
|
|
||||||
|
|
||||||
switch actionStyle {
|
switch actionStyle {
|
||||||
case .none:
|
case .none:
|
||||||
actionImageView.isHidden = true
|
actionImageView.isHidden = true
|
||||||
@ -75,6 +76,8 @@ final class AllFunctionMenuCell: UICollectionViewCell {
|
|||||||
|
|
||||||
iconView.tintColor = AppColor.primary
|
iconView.tintColor = AppColor.primary
|
||||||
iconView.contentMode = .scaleAspectFit
|
iconView.contentMode = .scaleAspectFit
|
||||||
|
iconView.preferredSymbolConfiguration = HomeMenuIconFactory.symbolConfiguration
|
||||||
|
|
||||||
|
|
||||||
contentStackView.axis = .vertical
|
contentStackView.axis = .vertical
|
||||||
contentStackView.alignment = .center
|
contentStackView.alignment = .center
|
||||||
|
|||||||
@ -128,7 +128,7 @@ final class HomeMenuCell: UICollectionViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apply(menu: HomeMenuItem) {
|
func apply(menu: HomeMenuItem) {
|
||||||
iconView.image = UIImage(named: menu.iconName) ?? UIImage(systemName: menu.iconName)
|
iconView.image = HomeMenuIconFactory.image(named: menu.iconName)
|
||||||
iconView.accessibilityIdentifier = menu.iconName
|
iconView.accessibilityIdentifier = menu.iconName
|
||||||
titleLabel.text = menu.title
|
titleLabel.text = menu.title
|
||||||
}
|
}
|
||||||
@ -147,6 +147,8 @@ final class HomeMenuCell: UICollectionViewCell {
|
|||||||
|
|
||||||
iconView.tintColor = AppColor.primary
|
iconView.tintColor = AppColor.primary
|
||||||
iconView.contentMode = .scaleAspectFit
|
iconView.contentMode = .scaleAspectFit
|
||||||
|
iconView.preferredSymbolConfiguration = HomeMenuIconFactory.symbolConfiguration
|
||||||
|
|
||||||
|
|
||||||
contentStackView.axis = .vertical
|
contentStackView.axis = .vertical
|
||||||
contentStackView.alignment = .center
|
contentStackView.alignment = .center
|
||||||
|
|||||||
@ -186,7 +186,7 @@ private final class AccountSelectionCell: UITableViewCell {
|
|||||||
private let avatarPlaceholderLabel = UILabel()
|
private let avatarPlaceholderLabel = UILabel()
|
||||||
private let titleLabel = UILabel()
|
private let titleLabel = UILabel()
|
||||||
private let subtitleLabel = UILabel()
|
private let subtitleLabel = UILabel()
|
||||||
private let phoneLabel = UILabel()
|
private let nameLabel = UILabel()
|
||||||
private let typeTag = UILabel()
|
private let typeTag = UILabel()
|
||||||
private let currentTag = UILabel()
|
private let currentTag = UILabel()
|
||||||
private let checkmark = UIImageView()
|
private let checkmark = UIImageView()
|
||||||
@ -214,8 +214,8 @@ private final class AccountSelectionCell: UITableViewCell {
|
|||||||
subtitleLabel.font = .systemFont(ofSize: 13)
|
subtitleLabel.font = .systemFont(ofSize: 13)
|
||||||
subtitleLabel.textColor = AppColor.text666
|
subtitleLabel.textColor = AppColor.text666
|
||||||
|
|
||||||
phoneLabel.font = .systemFont(ofSize: 12)
|
nameLabel.font = .systemFont(ofSize: 12)
|
||||||
phoneLabel.textColor = UIColor(hex: 0x9AA1AA)
|
nameLabel.textColor = UIColor(hex: 0x9AA1AA)
|
||||||
|
|
||||||
typeTag.font = .systemFont(ofSize: 11, weight: .semibold)
|
typeTag.font = .systemFont(ofSize: 11, weight: .semibold)
|
||||||
typeTag.textAlignment = .center
|
typeTag.textAlignment = .center
|
||||||
@ -239,7 +239,7 @@ private final class AccountSelectionCell: UITableViewCell {
|
|||||||
avatarView.addSubview(avatarPlaceholderLabel)
|
avatarView.addSubview(avatarPlaceholderLabel)
|
||||||
card.addSubview(titleLabel)
|
card.addSubview(titleLabel)
|
||||||
card.addSubview(subtitleLabel)
|
card.addSubview(subtitleLabel)
|
||||||
card.addSubview(phoneLabel)
|
card.addSubview(nameLabel)
|
||||||
card.addSubview(typeTag)
|
card.addSubview(typeTag)
|
||||||
card.addSubview(currentTag)
|
card.addSubview(currentTag)
|
||||||
card.addSubview(checkmark)
|
card.addSubview(checkmark)
|
||||||
@ -277,7 +277,7 @@ private final class AccountSelectionCell: UITableViewCell {
|
|||||||
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
|
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
|
||||||
}
|
}
|
||||||
|
|
||||||
phoneLabel.snp.makeConstraints { make in
|
nameLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
|
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
|
||||||
make.leading.equalTo(titleLabel)
|
make.leading.equalTo(titleLabel)
|
||||||
make.bottom.equalToSuperview().inset(16)
|
make.bottom.equalToSuperview().inset(16)
|
||||||
@ -304,8 +304,8 @@ private final class AccountSelectionCell: UITableViewCell {
|
|||||||
titleLabel.text = account.title.isEmpty ? account.accountTypeLabel : account.title
|
titleLabel.text = account.title.isEmpty ? account.accountTypeLabel : account.title
|
||||||
subtitleLabel.text = account.subtitle
|
subtitleLabel.text = account.subtitle
|
||||||
subtitleLabel.isHidden = account.subtitle.isEmpty
|
subtitleLabel.isHidden = account.subtitle.isEmpty
|
||||||
phoneLabel.text = account.phone
|
nameLabel.text = account.realName
|
||||||
phoneLabel.isHidden = account.phone.isEmpty
|
nameLabel.isHidden = account.realName.isEmpty
|
||||||
currentTag.isHidden = !account.isCurrent
|
currentTag.isHidden = !account.isCurrent
|
||||||
|
|
||||||
let avatarPlaceholderColor: UIColor
|
let avatarPlaceholderColor: UIColor
|
||||||
|
|||||||
@ -161,7 +161,7 @@ private final class AccountSwitchCell: UITableViewCell {
|
|||||||
private let titleLabel = UILabel()
|
private let titleLabel = UILabel()
|
||||||
private let currentTagLabel = UILabel()
|
private let currentTagLabel = UILabel()
|
||||||
private let subtitleLabel = UILabel()
|
private let subtitleLabel = UILabel()
|
||||||
private let phoneLabel = UILabel()
|
private let nameLabel = UILabel()
|
||||||
private let tagLabel = UILabel()
|
private let tagLabel = UILabel()
|
||||||
private let checkView = UIImageView()
|
private let checkView = UIImageView()
|
||||||
|
|
||||||
@ -197,8 +197,8 @@ private final class AccountSwitchCell: UITableViewCell {
|
|||||||
currentTagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
currentTagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
subtitleLabel.font = .systemFont(ofSize: 13)
|
subtitleLabel.font = .systemFont(ofSize: 13)
|
||||||
subtitleLabel.textColor = AppColor.text666
|
subtitleLabel.textColor = AppColor.text666
|
||||||
phoneLabel.font = .systemFont(ofSize: 12)
|
nameLabel.font = .systemFont(ofSize: 12)
|
||||||
phoneLabel.textColor = UIColor(hex: 0x9AA1AA)
|
nameLabel.textColor = UIColor(hex: 0x9AA1AA)
|
||||||
tagLabel.font = .systemFont(ofSize: 11, weight: .semibold)
|
tagLabel.font = .systemFont(ofSize: 11, weight: .semibold)
|
||||||
tagLabel.textAlignment = .center
|
tagLabel.textAlignment = .center
|
||||||
tagLabel.layer.cornerRadius = 4
|
tagLabel.layer.cornerRadius = 4
|
||||||
@ -210,7 +210,7 @@ private final class AccountSwitchCell: UITableViewCell {
|
|||||||
|
|
||||||
card.addSubview(titleStackView)
|
card.addSubview(titleStackView)
|
||||||
card.addSubview(subtitleLabel)
|
card.addSubview(subtitleLabel)
|
||||||
card.addSubview(phoneLabel)
|
card.addSubview(nameLabel)
|
||||||
card.addSubview(tagLabel)
|
card.addSubview(tagLabel)
|
||||||
card.addSubview(checkView)
|
card.addSubview(checkView)
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ private final class AccountSwitchCell: UITableViewCell {
|
|||||||
make.leading.equalTo(titleStackView)
|
make.leading.equalTo(titleStackView)
|
||||||
make.top.equalTo(titleStackView.snp.bottom).offset(4)
|
make.top.equalTo(titleStackView.snp.bottom).offset(4)
|
||||||
}
|
}
|
||||||
phoneLabel.snp.makeConstraints { make in
|
nameLabel.snp.makeConstraints { make in
|
||||||
make.leading.equalTo(titleLabel)
|
make.leading.equalTo(titleLabel)
|
||||||
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
|
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
|
||||||
}
|
}
|
||||||
@ -257,7 +257,9 @@ private final class AccountSwitchCell: UITableViewCell {
|
|||||||
titleLabel.text = account.title
|
titleLabel.text = account.title
|
||||||
currentTagLabel.isHidden = !isCurrent
|
currentTagLabel.isHidden = !isCurrent
|
||||||
subtitleLabel.text = account.subtitle
|
subtitleLabel.text = account.subtitle
|
||||||
phoneLabel.text = account.phone
|
subtitleLabel.isHidden = account.subtitle.isEmpty
|
||||||
|
nameLabel.text = account.realName
|
||||||
|
nameLabel.isHidden = account.realName.isEmpty
|
||||||
tagLabel.text = account.isStoreUser ? "门店" : "景区"
|
tagLabel.text = account.isStoreUser ? "门店" : "景区"
|
||||||
tagLabel.textColor = account.isStoreUser ? UIColor(hex: 0x0F9F6E) : UIColor(hex: 0x7C3AED)
|
tagLabel.textColor = account.isStoreUser ? UIColor(hex: 0x0F9F6E) : UIColor(hex: 0x7C3AED)
|
||||||
tagLabel.backgroundColor = account.isStoreUser ? UIColor(hex: 0xE8F8F1) : UIColor(hex: 0xF3ECFF)
|
tagLabel.backgroundColor = account.isStoreUser ? UIColor(hex: 0xE8F8F1) : UIColor(hex: 0xF3ECFF)
|
||||||
|
|||||||
@ -55,7 +55,6 @@ final class TravelAlbumEntryViewController: BaseViewController {
|
|||||||
cell.apply(album: album)
|
cell.apply(album: album)
|
||||||
cell.onShootUpload = { [weak self] in self?.pushWiredTransfer(album: album) }
|
cell.onShootUpload = { [weak self] in self?.pushWiredTransfer(album: album) }
|
||||||
cell.onAlbumCode = { [weak self] in self?.loadAlbumCode(album) }
|
cell.onAlbumCode = { [weak self] in self?.loadAlbumCode(album) }
|
||||||
cell.onShare = { [weak self] in self?.showShareSheet() }
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,18 +161,6 @@ final class TravelAlbumEntryViewController: BaseViewController {
|
|||||||
present(controller, animated: true)
|
present(controller, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showShareSheet() {
|
|
||||||
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
||||||
sheet.addAction(UIAlertAction(title: "微信好友", style: .default) { [weak self] _ in
|
|
||||||
self?.showToast("已选择微信好友")
|
|
||||||
})
|
|
||||||
sheet.addAction(UIAlertAction(title: "QQ好友", style: .default) { [weak self] _ in
|
|
||||||
self?.showToast("已选择QQ好友")
|
|
||||||
})
|
|
||||||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
|
||||||
present(sheet, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func pushWiredTransfer(album: TravelAlbum) {
|
private func pushWiredTransfer(album: TravelAlbum) {
|
||||||
let controller = WiredCameraTransferViewController(
|
let controller = WiredCameraTransferViewController(
|
||||||
viewModel: WiredCameraTransferViewModel(
|
viewModel: WiredCameraTransferViewModel(
|
||||||
@ -272,7 +259,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
|
|
||||||
var onShootUpload: (() -> Void)?
|
var onShootUpload: (() -> Void)?
|
||||||
var onAlbumCode: (() -> Void)?
|
var onAlbumCode: (() -> Void)?
|
||||||
var onShare: (() -> Void)?
|
|
||||||
|
|
||||||
private let cardView = UIView()
|
private let cardView = UIView()
|
||||||
private let coverView = UIImageView()
|
private let coverView = UIImageView()
|
||||||
@ -280,7 +266,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
private let nameLabel = UILabel()
|
private let nameLabel = UILabel()
|
||||||
private let timeLabel = UILabel()
|
private let timeLabel = UILabel()
|
||||||
private let phoneLabel = UILabel()
|
private let phoneLabel = UILabel()
|
||||||
private let shareButton = UIButton(type: .system)
|
|
||||||
private let codeButton = UIButton(type: .system)
|
private let codeButton = UIButton(type: .system)
|
||||||
private let uploadButton = UIButton(type: .system)
|
private let uploadButton = UIButton(type: .system)
|
||||||
|
|
||||||
@ -314,8 +299,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
phoneLabel.numberOfLines = 1
|
phoneLabel.numberOfLines = 1
|
||||||
phoneLabel.setContentCompressionResistancePriority(.required, for: .vertical)
|
phoneLabel.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
|
|
||||||
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
|
|
||||||
shareButton.tintColor = AppColor.primary
|
|
||||||
codeButton.setImage(UIImage(systemName: "qrcode"), for: .normal)
|
codeButton.setImage(UIImage(systemName: "qrcode"), for: .normal)
|
||||||
codeButton.tintColor = .black
|
codeButton.tintColor = .black
|
||||||
uploadButton.setImage(UIImage(systemName: "camera.fill"), for: .normal)
|
uploadButton.setImage(UIImage(systemName: "camera.fill"), for: .normal)
|
||||||
@ -332,7 +315,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
cardView.addSubview(nameLabel)
|
cardView.addSubview(nameLabel)
|
||||||
cardView.addSubview(timeLabel)
|
cardView.addSubview(timeLabel)
|
||||||
cardView.addSubview(phoneLabel)
|
cardView.addSubview(phoneLabel)
|
||||||
cardView.addSubview(shareButton)
|
|
||||||
cardView.addSubview(codeButton)
|
cardView.addSubview(codeButton)
|
||||||
cardView.addSubview(uploadButton)
|
cardView.addSubview(uploadButton)
|
||||||
|
|
||||||
@ -347,20 +329,15 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
make.size.equalTo(42)
|
make.size.equalTo(42)
|
||||||
}
|
}
|
||||||
shareButton.snp.makeConstraints { make in
|
|
||||||
make.top.equalToSuperview().offset(12)
|
|
||||||
make.trailing.equalTo(codeButton.snp.leading).offset(-8)
|
|
||||||
make.size.equalTo(26)
|
|
||||||
}
|
|
||||||
codeButton.snp.makeConstraints { make in
|
codeButton.snp.makeConstraints { make in
|
||||||
make.top.equalTo(shareButton)
|
make.top.equalToSuperview().offset(12)
|
||||||
make.trailing.equalToSuperview().offset(-10)
|
make.trailing.equalToSuperview().offset(-10)
|
||||||
make.size.equalTo(26)
|
make.size.equalTo(26)
|
||||||
}
|
}
|
||||||
nameLabel.snp.makeConstraints { make in
|
nameLabel.snp.makeConstraints { make in
|
||||||
make.top.equalToSuperview().offset(14)
|
make.top.equalToSuperview().offset(14)
|
||||||
make.leading.equalTo(coverView.snp.trailing).offset(12)
|
make.leading.equalTo(coverView.snp.trailing).offset(12)
|
||||||
make.trailing.equalTo(shareButton.snp.leading).offset(-8)
|
make.trailing.equalTo(codeButton.snp.leading).offset(-8)
|
||||||
}
|
}
|
||||||
timeLabel.snp.makeConstraints { make in
|
timeLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(nameLabel.snp.bottom).offset(7)
|
make.top.equalTo(nameLabel.snp.bottom).offset(7)
|
||||||
@ -381,7 +358,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
|
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
|
||||||
shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)
|
|
||||||
codeButton.addTarget(self, action: #selector(codeTapped), for: .touchUpInside)
|
codeButton.addTarget(self, action: #selector(codeTapped), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +380,6 @@ private final class TravelAlbumTaskCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func uploadTapped() { onShootUpload?() }
|
@objc private func uploadTapped() { onShootUpload?() }
|
||||||
@objc private func shareTapped() { onShare?() }
|
|
||||||
@objc private func codeTapped() { onAlbumCode?() }
|
@objc private func codeTapped() { onAlbumCode?() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,10 +40,9 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
private let refreshButton = UIButton(type: .system)
|
private let refreshButton = UIButton(type: .system)
|
||||||
private let helpLabel = UILabel()
|
private let helpLabel = UILabel()
|
||||||
private let chipsStack = UIStackView()
|
private let chipsStack = UIStackView()
|
||||||
private let retouchButton = UIButton(type: .system)
|
private let retouchButton = WiredTransferSettingChipButton()
|
||||||
private let formatButton = UIButton(type: .system)
|
private let formatButton = WiredTransferSettingChipButton()
|
||||||
private let modeButton = UIButton(type: .system)
|
private let modeButton = WiredTransferSettingChipButton(showsChevron: true)
|
||||||
private let modeChevronView = UIImageView()
|
|
||||||
private let settingsStatsDivider = UIView()
|
private let settingsStatsDivider = UIView()
|
||||||
private let statsCard = UIStackView()
|
private let statsCard = UIStackView()
|
||||||
private let firstStatsDivider = UIView()
|
private let firstStatsDivider = UIView()
|
||||||
@ -160,19 +159,10 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
chipsStack.spacing = 6
|
chipsStack.spacing = 6
|
||||||
chipsStack.distribution = .fillEqually
|
chipsStack.distribution = .fillEqually
|
||||||
[retouchButton, formatButton, modeButton].forEach {
|
[retouchButton, formatButton, modeButton].forEach {
|
||||||
configureChipButton($0)
|
|
||||||
chipsStack.addArrangedSubview($0)
|
chipsStack.addArrangedSubview($0)
|
||||||
}
|
}
|
||||||
retouchButton.isUserInteractionEnabled = false
|
retouchButton.isUserInteractionEnabled = false
|
||||||
formatButton.isUserInteractionEnabled = false
|
formatButton.isUserInteractionEnabled = false
|
||||||
modeButton.setConfigurationContentInsets(
|
|
||||||
NSDirectionalEdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 32)
|
|
||||||
)
|
|
||||||
modeChevronView.image = UIImage(systemName: "chevron.down")?
|
|
||||||
.withConfiguration(UIImage.SymbolConfiguration(pointSize: 13, weight: .semibold))
|
|
||||||
modeChevronView.tintColor = AppColor.textTertiary
|
|
||||||
modeChevronView.contentMode = .scaleAspectFit
|
|
||||||
modeChevronView.isUserInteractionEnabled = false
|
|
||||||
settingsStatsDivider.backgroundColor = AppColor.border
|
settingsStatsDivider.backgroundColor = AppColor.border
|
||||||
|
|
||||||
statsCard.axis = .horizontal
|
statsCard.axis = .horizontal
|
||||||
@ -269,7 +259,6 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
view.addSubview(emptyLabel)
|
view.addSubview(emptyLabel)
|
||||||
view.addSubview(bottomBar)
|
view.addSubview(bottomBar)
|
||||||
bottomBar.addSubview(bottomButtonsStack)
|
bottomBar.addSubview(bottomButtonsStack)
|
||||||
modeButton.addSubview(modeChevronView)
|
|
||||||
bottomButtonsStack.addArrangedSubview(batchButton)
|
bottomButtonsStack.addArrangedSubview(batchButton)
|
||||||
bottomButtonsStack.addArrangedSubview(specifyButton)
|
bottomButtonsStack.addArrangedSubview(specifyButton)
|
||||||
bottomButtonsStack.addArrangedSubview(albumImportButton)
|
bottomButtonsStack.addArrangedSubview(albumImportButton)
|
||||||
@ -318,11 +307,6 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
make.leading.trailing.equalToSuperview().inset(14)
|
make.leading.trailing.equalToSuperview().inset(14)
|
||||||
make.height.equalTo(34)
|
make.height.equalTo(34)
|
||||||
}
|
}
|
||||||
modeChevronView.snp.makeConstraints { make in
|
|
||||||
make.trailing.equalToSuperview().offset(-10)
|
|
||||||
make.centerY.equalToSuperview()
|
|
||||||
make.size.equalTo(16)
|
|
||||||
}
|
|
||||||
settingsStatsDivider.snp.makeConstraints { make in
|
settingsStatsDivider.snp.makeConstraints { make in
|
||||||
make.top.equalTo(chipsStack.snp.bottom).offset(12)
|
make.top.equalTo(chipsStack.snp.bottom).offset(12)
|
||||||
make.leading.trailing.equalToSuperview()
|
make.leading.trailing.equalToSuperview()
|
||||||
@ -431,9 +415,9 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
statusLabel.backgroundColor = (isFailed ? AppColor.danger : AppColor.primary).withAlphaComponent(0.10)
|
statusLabel.backgroundColor = (isFailed ? AppColor.danger : AppColor.primary).withAlphaComponent(0.10)
|
||||||
refreshButton.setTitle(viewModel.actionButtonText, for: .normal)
|
refreshButton.setTitle(viewModel.actionButtonText, for: .normal)
|
||||||
|
|
||||||
retouchButton.setTitle(viewModel.retouchOption, for: .normal)
|
retouchButton.apply(title: viewModel.retouchOption)
|
||||||
formatButton.setTitle("JPG", for: .normal)
|
formatButton.apply(title: "JPG")
|
||||||
modeButton.setTitle(viewModel.transferModeOption, for: .normal)
|
modeButton.apply(title: viewModel.transferModeOption)
|
||||||
helpLabel.attributedText = helpText(viewModel.sonyMTPHint)
|
helpLabel.attributedText = helpText(viewModel.sonyMTPHint)
|
||||||
|
|
||||||
rebuildStats()
|
rebuildStats()
|
||||||
@ -642,19 +626,6 @@ final class WiredCameraTransferViewController: BaseViewController {
|
|||||||
statsCard.bringSubviewToFront(secondStatsDivider)
|
statsCard.bringSubviewToFront(secondStatsDivider)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureChipButton(_ button: UIButton) {
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 11)
|
|
||||||
button.setTitleColor(AppColor.textTertiary, for: .normal)
|
|
||||||
button.backgroundColor = AppColor.pageBackground
|
|
||||||
button.layer.cornerRadius = 8
|
|
||||||
button.layer.borderWidth = 1
|
|
||||||
button.layer.borderColor = AppColor.border.cgColor
|
|
||||||
button.contentHorizontalAlignment = .leading
|
|
||||||
button.setConfigurationContentInsets(
|
|
||||||
NSDirectionalEdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeStatButton(title: String, count: Int, selected: Bool, danger: Bool) -> UIButton {
|
private func makeStatButton(title: String, count: Int, selected: Bool, danger: Bool) -> UIButton {
|
||||||
var config = UIButton.Configuration.plain()
|
var config = UIButton.Configuration.plain()
|
||||||
config.title = "\(count)"
|
config.title = "\(count)"
|
||||||
|
|||||||
@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// WiredTransferSettingChipButton.swift
|
||||||
|
// suixinkan
|
||||||
|
//
|
||||||
|
|
||||||
|
import SnapKit
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// 有线传输页设置 chip 按钮,通过子视图展示标题与可选下拉箭头,不依赖 `UIButton` 原生 title 样式。
|
||||||
|
final class WiredTransferSettingChipButton: UIButton {
|
||||||
|
private let titleLabelView = UILabel()
|
||||||
|
private let chevronView = UIImageView()
|
||||||
|
private var titleTrailingToSuperviewConstraint: Constraint?
|
||||||
|
private var titleTrailingToChevronConstraint: Constraint?
|
||||||
|
|
||||||
|
/// 是否展示右侧下拉箭头。
|
||||||
|
var showsChevron: Bool {
|
||||||
|
didSet { updateChevronLayout() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 当前展示的标题文案。
|
||||||
|
private(set) var titleText: String = ""
|
||||||
|
|
||||||
|
/// 创建设置 chip 按钮。
|
||||||
|
/// - Parameter showsChevron: 是否展示下拉箭头,默认不展示。
|
||||||
|
init(showsChevron: Bool = false) {
|
||||||
|
self.showsChevron = showsChevron
|
||||||
|
super.init(frame: .zero)
|
||||||
|
setupUI()
|
||||||
|
updateChevronLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新 chip 标题文案。
|
||||||
|
func apply(title: String) {
|
||||||
|
titleText = title
|
||||||
|
titleLabelView.text = title
|
||||||
|
accessibilityLabel = title
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
backgroundColor = AppColor.pageBackground
|
||||||
|
layer.cornerRadius = 8
|
||||||
|
layer.borderWidth = 1
|
||||||
|
layer.borderColor = AppColor.border.cgColor
|
||||||
|
|
||||||
|
titleLabelView.font = .systemFont(ofSize: 11)
|
||||||
|
titleLabelView.textColor = AppColor.textTertiary
|
||||||
|
titleLabelView.lineBreakMode = .byTruncatingTail
|
||||||
|
titleLabelView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
chevronView.image = UIImage(systemName: "chevron.down")?
|
||||||
|
.withConfiguration(UIImage.SymbolConfiguration(pointSize: 13, weight: .semibold))
|
||||||
|
chevronView.tintColor = AppColor.textTertiary
|
||||||
|
chevronView.contentMode = .scaleAspectFit
|
||||||
|
chevronView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
addSubview(titleLabelView)
|
||||||
|
addSubview(chevronView)
|
||||||
|
|
||||||
|
titleLabelView.snp.makeConstraints { make in
|
||||||
|
make.leading.equalToSuperview().offset(10)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
titleTrailingToSuperviewConstraint = make.trailing.lessThanOrEqualToSuperview().offset(-10).constraint
|
||||||
|
titleTrailingToChevronConstraint = make.trailing.lessThanOrEqualTo(chevronView.snp.leading).offset(-4).constraint
|
||||||
|
}
|
||||||
|
chevronView.snp.makeConstraints { make in
|
||||||
|
make.trailing.equalToSuperview().offset(-10)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.size.equalTo(16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateChevronLayout() {
|
||||||
|
chevronView.isHidden = !showsChevron
|
||||||
|
if showsChevron {
|
||||||
|
titleTrailingToSuperviewConstraint?.deactivate()
|
||||||
|
titleTrailingToChevronConstraint?.activate()
|
||||||
|
} else {
|
||||||
|
titleTrailingToChevronConstraint?.deactivate()
|
||||||
|
titleTrailingToSuperviewConstraint?.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1187,7 +1187,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||||
button.titleLabel?.minimumScaleFactor = 0.82
|
button.titleLabel?.minimumScaleFactor = 0.82
|
||||||
button.setConfigurationContentInsets(
|
button.setConfigurationContentInsets(
|
||||||
NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
|
NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
|
||||||
)
|
)
|
||||||
button.configuration?.imagePlacement = .leading
|
button.configuration?.imagePlacement = .leading
|
||||||
button.configuration?.imagePadding = 4
|
button.configuration?.imagePadding = 4
|
||||||
|
|||||||
@ -328,14 +328,7 @@ final class WildReportRiskMapViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func makeRadarHeader(clue: WildReportRadarActiveClue) -> UIView {
|
private func makeRadarHeader(clue: WildReportRadarActiveClue) -> UIView {
|
||||||
let container = UIStackView()
|
let container = UIView()
|
||||||
container.axis = .vertical
|
|
||||||
container.spacing = 6
|
|
||||||
|
|
||||||
let row = UIStackView()
|
|
||||||
row.axis = .horizontal
|
|
||||||
row.alignment = .top
|
|
||||||
row.spacing = 12
|
|
||||||
|
|
||||||
let iconWrap = UIView()
|
let iconWrap = UIView()
|
||||||
iconWrap.backgroundColor = headerIconColor(for: clue)
|
iconWrap.backgroundColor = headerIconColor(for: clue)
|
||||||
@ -348,17 +341,15 @@ final class WildReportRiskMapViewController: BaseViewController {
|
|||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
make.size.equalTo(20)
|
make.size.equalTo(20)
|
||||||
}
|
}
|
||||||
iconWrap.snp.makeConstraints { make in
|
|
||||||
make.size.equalTo(44)
|
|
||||||
}
|
|
||||||
|
|
||||||
let textStack = UIStackView()
|
let badge = WildReportRiskStatusBadge(text: clue.statusText, color: statusColor(for: clue))
|
||||||
textStack.axis = .vertical
|
badge.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
textStack.spacing = 6
|
badge.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
var reportMetaText: String?
|
|
||||||
|
let title: UILabel
|
||||||
|
var metaText: String?
|
||||||
if isReportClue(clue) {
|
if isReportClue(clue) {
|
||||||
let title = WildReportUI.label("线索 ID:\(clue.id)", font: .systemFont(ofSize: 16, weight: .bold), color: AppColor.textPrimary, lines: 2)
|
title = WildReportUI.label("线索 ID:\(clue.id)", font: .systemFont(ofSize: 14, weight: .bold), color: AppColor.textPrimary, lines: 1)
|
||||||
textStack.addArrangedSubview(title)
|
|
||||||
var metaParts: [String] = []
|
var metaParts: [String] = []
|
||||||
if let distance = clue.distanceText ?? clue.distance {
|
if let distance = clue.distanceText ?? clue.distance {
|
||||||
metaParts.append("距离:距离您\(distance)")
|
metaParts.append("距离:距离您\(distance)")
|
||||||
@ -366,41 +357,58 @@ final class WildReportRiskMapViewController: BaseViewController {
|
|||||||
if let updatedAt = clue.updatedAt {
|
if let updatedAt = clue.updatedAt {
|
||||||
metaParts.append("时间:\(updatedAt)")
|
metaParts.append("时间:\(updatedAt)")
|
||||||
}
|
}
|
||||||
reportMetaText = metaParts.isEmpty ? nil : metaParts.joined(separator: " ")
|
metaText = metaParts.isEmpty ? nil : metaParts.joined(separator: " ")
|
||||||
} else {
|
} else {
|
||||||
let title = WildReportUI.label(clue.displayTitle, font: .systemFont(ofSize: 16, weight: .bold), color: AppColor.textPrimary, lines: 2)
|
title = WildReportUI.label(clue.displayTitle, font: .systemFont(ofSize: 16, weight: .bold), color: AppColor.textPrimary, lines: 2)
|
||||||
textStack.addArrangedSubview(title)
|
|
||||||
if clue.type == .green {
|
if clue.type == .green {
|
||||||
let metaText = [clue.distance.map { "距蓝点 \($0)" }, clue.direction, clue.updatedAt].compactMap { $0 }.joined(separator: " ")
|
let text = [clue.distance.map { "距蓝点 \($0)" }, clue.direction, clue.updatedAt].compactMap { $0 }.joined(separator: " ")
|
||||||
if !metaText.isEmpty {
|
metaText = text.isEmpty ? nil : text
|
||||||
textStack.addArrangedSubview(WildReportUI.label(metaText, font: .systemFont(ofSize: 12), color: AppColor.textSecondary))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let badge = WildReportRiskStatusBadge(text: clue.statusText, color: statusColor(for: clue))
|
container.addSubview(iconWrap)
|
||||||
row.addArrangedSubview(iconWrap)
|
container.addSubview(badge)
|
||||||
row.addArrangedSubview(textStack)
|
container.addSubview(title)
|
||||||
row.addArrangedSubview(badge)
|
|
||||||
textStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
||||||
badge.setContentHuggingPriority(.required, for: .horizontal)
|
|
||||||
container.addArrangedSubview(row)
|
|
||||||
|
|
||||||
if let reportMetaText {
|
iconWrap.snp.makeConstraints { make in
|
||||||
let metaRow = UIStackView()
|
make.top.leading.equalToSuperview()
|
||||||
metaRow.axis = .horizontal
|
make.size.equalTo(44)
|
||||||
metaRow.spacing = 0
|
|
||||||
let spacer = UIView()
|
|
||||||
spacer.snp.makeConstraints { make in
|
|
||||||
make.width.equalTo(56)
|
|
||||||
}
|
}
|
||||||
let metaLabel = WildReportUI.label(reportMetaText, font: .systemFont(ofSize: 12), color: AppColor.textSecondary)
|
badge.snp.makeConstraints { make in
|
||||||
metaLabel.textAlignment = .right
|
make.top.trailing.equalToSuperview()
|
||||||
metaLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
||||||
metaRow.addArrangedSubview(spacer)
|
|
||||||
metaRow.addArrangedSubview(metaLabel)
|
|
||||||
container.addArrangedSubview(metaRow)
|
|
||||||
}
|
}
|
||||||
|
title.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.leading.equalTo(iconWrap.snp.trailing).offset(12)
|
||||||
|
make.trailing.lessThanOrEqualTo(badge.snp.leading).offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let metaText {
|
||||||
|
let metaLabel = WildReportUI.label(metaText, font: .systemFont(ofSize: 12), color: AppColor.textSecondary, lines: 0)
|
||||||
|
container.addSubview(metaLabel)
|
||||||
|
metaLabel.snp.makeConstraints { make in
|
||||||
|
make.top.equalTo(title.snp.bottom).offset(6)
|
||||||
|
make.leading.equalTo(title)
|
||||||
|
make.trailing.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
iconWrap.snp.makeConstraints { make in
|
||||||
|
make.bottom.lessThanOrEqualToSuperview()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[iconWrap, title, badge].forEach { view in
|
||||||
|
view.snp.makeConstraints { make in
|
||||||
|
make.bottom.lessThanOrEqualToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iconWrap.snp.makeConstraints { make in
|
||||||
|
make.bottom.equalToSuperview().priority(.high)
|
||||||
|
}
|
||||||
|
title.snp.makeConstraints { make in
|
||||||
|
make.bottom.equalToSuperview().priority(.high)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,8 +53,9 @@ final class AccountSwitchViewModelTests: XCTestCase {
|
|||||||
accountType: V9ScenicUser.accountTypeValue,
|
accountType: V9ScenicUser.accountTypeValue,
|
||||||
businessUserId: 0,
|
businessUserId: 0,
|
||||||
title: "景区A",
|
title: "景区A",
|
||||||
subtitle: "张三",
|
subtitle: "角色A",
|
||||||
phone: "13800138000",
|
phone: "13800138000",
|
||||||
|
realName: "张三",
|
||||||
avatar: "",
|
avatar: "",
|
||||||
scenicName: "景区A",
|
scenicName: "景区A",
|
||||||
storeId: nil,
|
storeId: nil,
|
||||||
|
|||||||
@ -29,6 +29,7 @@ final class AuthModelsTests: XCTestCase {
|
|||||||
"phone": "13800138000",
|
"phone": "13800138000",
|
||||||
"scenic_id": 10,
|
"scenic_id": 10,
|
||||||
"scenic_name": "示例景区",
|
"scenic_name": "示例景区",
|
||||||
|
"role_name": "摄影师",
|
||||||
"is_current": false
|
"is_current": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -47,6 +48,7 @@ final class AuthModelsTests: XCTestCase {
|
|||||||
"scenic_name": "示例景区",
|
"scenic_name": "示例景区",
|
||||||
"store_id": 20,
|
"store_id": 20,
|
||||||
"store_name": "示例门店",
|
"store_name": "示例门店",
|
||||||
|
"role_name": "店长",
|
||||||
"is_current": false
|
"is_current": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -59,7 +61,7 @@ final class AuthModelsTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(response.token, "person-temp-token")
|
XCTAssertEqual(response.token, "person-temp-token")
|
||||||
XCTAssertEqual(response.accounts.map(\.businessUserId), [101, 201])
|
XCTAssertEqual(response.accounts.map(\.businessUserId), [101, 201])
|
||||||
XCTAssertEqual(response.accounts.map(\.subtitle), ["scenic_operator", "store_admin"])
|
XCTAssertEqual(response.accounts.map(\.subtitle), ["示例景区 · 摄影师", "示例景区 · 店长"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testV9AuthResponseDecodesMissingAccountListsAsEmpty() throws {
|
func testV9AuthResponseDecodesMissingAccountListsAsEmpty() throws {
|
||||||
@ -96,13 +98,15 @@ final class AuthModelsTests: XCTestCase {
|
|||||||
"phone": "13800138000",
|
"phone": "13800138000",
|
||||||
"scenic_id": 10,
|
"scenic_id": 10,
|
||||||
"scenic_name": "示例景区",
|
"scenic_name": "示例景区",
|
||||||
|
"role_name": "摄影师",
|
||||||
"is_current": false
|
"is_current": false
|
||||||
}
|
}
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let user = try JSONDecoder().decode(V9ScenicUser.self, from: json)
|
let user = try JSONDecoder().decode(V9ScenicUser.self, from: json)
|
||||||
|
|
||||||
XCTAssertEqual(user.businessUserId, 101)
|
XCTAssertEqual(user.businessUserId, 101)
|
||||||
XCTAssertEqual(user.toAccountSwitchAccount().subtitle, "scenic_operator")
|
XCTAssertEqual(user.toAccountSwitchAccount().subtitle, "示例景区 · 摄影师")
|
||||||
|
XCTAssertEqual(user.toAccountSwitchAccount().realName, "张三")
|
||||||
XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(ssUserId: 101))
|
XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(ssUserId: 101))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,13 +126,15 @@ final class AuthModelsTests: XCTestCase {
|
|||||||
"scenic_name": "示例景区",
|
"scenic_name": "示例景区",
|
||||||
"store_id": 20,
|
"store_id": 20,
|
||||||
"store_name": "示例门店",
|
"store_name": "示例门店",
|
||||||
|
"role_name": "店长",
|
||||||
"is_current": false
|
"is_current": false
|
||||||
}
|
}
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let user = try JSONDecoder().decode(V9StoreUser.self, from: json)
|
let user = try JSONDecoder().decode(V9StoreUser.self, from: json)
|
||||||
|
|
||||||
XCTAssertEqual(user.businessUserId, 201)
|
XCTAssertEqual(user.businessUserId, 201)
|
||||||
XCTAssertEqual(user.toAccountSwitchAccount().subtitle, "store_admin")
|
XCTAssertEqual(user.toAccountSwitchAccount().subtitle, "示例景区 · 店长")
|
||||||
|
XCTAssertEqual(user.toAccountSwitchAccount().realName, "张三")
|
||||||
XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(storeUserId: 201))
|
XCTAssertEqual(user.toAccountSwitchAccount().toSetUserRequest(), SetUserRequest(storeUserId: 201))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
suixinkanTests/HomeMenuIconFactoryTests.swift
Normal file
44
suixinkanTests/HomeMenuIconFactoryTests.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// HomeMenuIconFactoryTests.swift
|
||||||
|
// suixinkanTests
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
@testable import suixinkan
|
||||||
|
|
||||||
|
/// 首页菜单图标加载测试。
|
||||||
|
final class HomeMenuIconFactoryTests: XCTestCase {
|
||||||
|
|
||||||
|
func testAssetIconTakesPrecedence() {
|
||||||
|
let image = HomeMenuIconFactory.image(named: "home_menu_space")
|
||||||
|
|
||||||
|
XCTAssertNotNil(image)
|
||||||
|
XCTAssertNil(UIImage(systemName: "home_menu_space"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSystemSymbolMatchesSharedConfiguration() {
|
||||||
|
let expected = UIImage(
|
||||||
|
systemName: "ellipsis",
|
||||||
|
withConfiguration: HomeMenuIconFactory.symbolConfiguration
|
||||||
|
)
|
||||||
|
let actual = HomeMenuIconFactory.image(named: "ellipsis")
|
||||||
|
|
||||||
|
XCTAssertNotNil(actual)
|
||||||
|
XCTAssertEqual(actual?.size, expected?.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSystemSymbolDiffersFromUnconfiguredDefault() {
|
||||||
|
let configured = HomeMenuIconFactory.image(named: "ellipsis")
|
||||||
|
let plain = UIImage(systemName: "ellipsis")
|
||||||
|
|
||||||
|
XCTAssertNotNil(configured)
|
||||||
|
XCTAssertNotNil(plain)
|
||||||
|
XCTAssertNotEqual(configured?.size, plain?.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testComplexSystemSymbolResolves() {
|
||||||
|
XCTAssertNotNil(HomeMenuIconFactory.image(named: "dot.radiowaves.left.and.right"))
|
||||||
|
XCTAssertNotNil(HomeMenuIconFactory.image(named: "rectangle.stack.badge.plus"))
|
||||||
|
}
|
||||||
|
}
|
||||||
29
suixinkanTests/WiredTransferSettingChipButtonTests.swift
Normal file
29
suixinkanTests/WiredTransferSettingChipButtonTests.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// WiredTransferSettingChipButtonTests.swift
|
||||||
|
// suixinkanTests
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import suixinkan
|
||||||
|
|
||||||
|
final class WiredTransferSettingChipButtonTests: XCTestCase {
|
||||||
|
@MainActor
|
||||||
|
func testApplyTitleUpdatesAccessibilityLabel() {
|
||||||
|
let button = WiredTransferSettingChipButton()
|
||||||
|
button.apply(title: "AI修图")
|
||||||
|
XCTAssertEqual(button.titleText, "AI修图")
|
||||||
|
XCTAssertEqual(button.accessibilityLabel, "AI修图")
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testShowsChevronDefaultsToFalse() {
|
||||||
|
let button = WiredTransferSettingChipButton()
|
||||||
|
XCTAssertFalse(button.showsChevron)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testInitWithShowsChevronTrue() {
|
||||||
|
let button = WiredTransferSettingChipButton(showsChevron: true)
|
||||||
|
XCTAssertTrue(button.showsChevron)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user