665 lines
21 KiB
Swift
665 lines
21 KiB
Swift
//
|
|
// MapView.swift
|
|
// QuickLocation
|
|
//
|
|
// Created based on Lanhu design: 1.主页.-成员
|
|
//
|
|
|
|
import UIKit
|
|
import RxSwift
|
|
#if !targetEnvironment(simulator)
|
|
import MAMapKit
|
|
#endif
|
|
|
|
final class MapView: UIView {
|
|
|
|
var disposeBag = DisposeBag()
|
|
|
|
// MARK: - Design Constants (iOS points, from Lanhu design "1.主页.-成员" 375pt wide)
|
|
private let topNavHeight: CGFloat = 44
|
|
private let controlButtonSize: CGFloat = 14
|
|
private let controlStripWidth: CGFloat = 20
|
|
private let controlStripCornerRadius: CGFloat = 10
|
|
private let panelHeaderHeight: CGFloat = 70
|
|
|
|
private var bottomPanelHeightConstraint: NSLayoutConstraint?
|
|
|
|
// MARK: - Fonts
|
|
private let douyuFont: (CGFloat) -> UIFont? = { size in
|
|
UIFont(name: "DOUYU Font", size: size)
|
|
}
|
|
private let pingFangMedium: (CGFloat) -> UIFont = { size in
|
|
UIFont(name: "PingFangSC-Medium", size: size) ?? UIFont.systemFont(ofSize: size, weight: .medium)
|
|
}
|
|
private let pingFangSemibold: (CGFloat) -> UIFont = { size in
|
|
UIFont(name: "PingFangSC-Semibold", size: size) ?? UIFont.systemFont(ofSize: size, weight: .semibold)
|
|
}
|
|
private let pingFangRegular: (CGFloat) -> UIFont = { size in
|
|
UIFont(name: "PingFangSC-Regular", size: size) ?? UIFont.systemFont(ofSize: size, weight: .regular)
|
|
}
|
|
|
|
// MARK: - Top Navigation Bar
|
|
private(set) lazy var topNavBar: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .clear
|
|
return v
|
|
}()
|
|
|
|
private(set) lazy var avatarButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.layer.cornerRadius = 18
|
|
btn.clipsToBounds = true
|
|
btn.backgroundColor = UIColor(hexStr: "#E0E0E0")
|
|
btn.setImage(UIImage(named: "map_avatar_1"), for: .normal)
|
|
return btn
|
|
}()
|
|
|
|
private lazy var titleLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.text = "我的圈子"
|
|
l.font = pingFangMedium(14)
|
|
l.textColor = UIColor(hexStr: "#0F2846")
|
|
return l
|
|
}()
|
|
|
|
private lazy var goldCircleIcon: UIImageView = {
|
|
let iv = UIImageView()
|
|
iv.image = UIImage(named: "Map/map_circle_icon")
|
|
iv.contentMode = .scaleAspectFit
|
|
return iv
|
|
}()
|
|
|
|
private(set) lazy var dropdownArrowButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(systemName: "chevron.down"), for: .normal)
|
|
btn.tintColor = UIColor(hexStr: "#0F2846")
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Map View
|
|
#if !targetEnvironment(simulator)
|
|
lazy var mapView: MAMapView = {
|
|
let mv = MAMapView()
|
|
mv.zoomLevel = 15
|
|
mv.minZoomLevel = 3
|
|
mv.maxZoomLevel = 20
|
|
mv.showsUserLocation = true
|
|
mv.userTrackingMode = .follow
|
|
mv.showsCompass = false
|
|
mv.showsScale = false
|
|
mv.isRotateEnabled = false
|
|
mv.isRotateCameraEnabled = false
|
|
return mv
|
|
}()
|
|
#else
|
|
lazy var mapPlaceholderView: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = UIColor(hexStr: "#EDEDED")
|
|
let label = UILabel()
|
|
label.text = "Map requires a real device"
|
|
label.font = UIFont.systemFont(ofSize: 14)
|
|
label.textColor = UIColor(hexStr: "#999999")
|
|
label.textAlignment = .center
|
|
v.addSubview(label)
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
label.centerXAnchor.constraint(equalTo: v.centerXAnchor),
|
|
label.centerYAnchor.constraint(equalTo: v.centerYAnchor)
|
|
])
|
|
return v
|
|
}()
|
|
#endif
|
|
|
|
// MARK: - Map Controls (Left Side)
|
|
private lazy var controlStrip: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
|
v.layer.cornerRadius = controlStripCornerRadius
|
|
v.clipsToBounds = true
|
|
return v
|
|
}()
|
|
|
|
private func makeControlButton(icon: String, text: String) -> UIView {
|
|
let container = UIView()
|
|
|
|
let circleBtn = UIView()
|
|
circleBtn.backgroundColor = .white
|
|
circleBtn.layer.cornerRadius = controlButtonSize / 2
|
|
container.addSubview(circleBtn)
|
|
|
|
let imgView = UIImageView()
|
|
imgView.image = UIImage(named: "Map/\(icon)")
|
|
imgView.contentMode = .scaleAspectFit
|
|
circleBtn.addSubview(imgView)
|
|
|
|
let label = UILabel()
|
|
label.text = text
|
|
label.font = pingFangMedium(8)
|
|
label.textColor = .white
|
|
label.textAlignment = .center
|
|
container.addSubview(label)
|
|
|
|
circleBtn.layoutChain
|
|
.top(0).centerX()
|
|
.size(CGSize(width: controlButtonSize, height: controlButtonSize))
|
|
|
|
imgView.layoutChain
|
|
.center().size(CGSize(width: 8, height: 8))
|
|
|
|
label.layoutChain
|
|
.topToBottomOfView(circleBtn, offset: 3)
|
|
.centerX().bottom()
|
|
|
|
return container
|
|
}
|
|
|
|
private(set) lazy var sosButton: UIView = makeControlButton(icon: "map_btn_sos", text: "SOS")
|
|
private(set) lazy var checkinButton: UIView = makeControlButton(icon: "map_btn_checkin", text: "签到")
|
|
private(set) lazy var bubbleButton: UIView = makeControlButton(icon: "map_btn_trip", text: "气泡")
|
|
|
|
// MARK: - Siren Button (left of control strip)
|
|
private(set) lazy var sirenButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.backgroundColor = .clear
|
|
btn.setImage(UIImage(named: "Map/map_member_bubble_large"), for: .normal)
|
|
btn.contentMode = .scaleAspectFit
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Location Button (Bottom-Right)
|
|
private(set) lazy var locationButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.backgroundColor = UIColor.black.withAlphaComponent(0.4)
|
|
btn.layer.cornerRadius = 10
|
|
btn.setImage(UIImage(named: "Map/map_current_location"), for: .normal)
|
|
btn.contentMode = .center
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Announcement Bar
|
|
private(set) lazy var announcementBar: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = UIColor.white
|
|
v.layer.cornerRadius = 11
|
|
v.layer.shadowColor = UIColor.black.withAlphaComponent(0.08).cgColor
|
|
v.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
v.layer.shadowRadius = 6
|
|
v.layer.shadowOpacity = 1
|
|
v.isHidden = true
|
|
return v
|
|
}()
|
|
|
|
private lazy var announcementLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = pingFangRegular(11)
|
|
l.textColor = UIColor(hexStr: "#0F2846")
|
|
return l
|
|
}()
|
|
|
|
private(set) lazy var announcementCloseBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(systemName: "xmark"), for: .normal)
|
|
btn.tintColor = UIColor(hexStr: "#999999")
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Bottom Panel
|
|
private(set) lazy var bottomPanel: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .white
|
|
v.layer.cornerRadius = 16
|
|
v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
v.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
|
v.layer.shadowOffset = CGSize(width: 0, height: -2)
|
|
v.layer.shadowRadius = 6
|
|
v.layer.shadowOpacity = 1
|
|
return v
|
|
}()
|
|
|
|
// Panel header background image
|
|
private lazy var panelHeaderBg: UIImageView = {
|
|
let iv = UIImageView()
|
|
iv.image = UIImage(named: "Map/map_top_panel")
|
|
iv.contentMode = .scaleToFill
|
|
return iv
|
|
}()
|
|
|
|
private lazy var circleIcon: UIImageView = {
|
|
let iv = UIImageView()
|
|
iv.image = UIImage(named: "Map/map_circle_icon")
|
|
iv.contentMode = .scaleAspectFit
|
|
return iv
|
|
}()
|
|
|
|
private lazy var memberListTitleLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.text = "圈子成员"
|
|
l.font = douyuFont(16) ?? pingFangSemibold(16)
|
|
l.textColor = .white
|
|
return l
|
|
}()
|
|
|
|
private(set) lazy var memberCountLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = pingFangRegular(11)
|
|
l.textColor = UIColor(hexStr: "#0F2846").withAlphaComponent(0.7)
|
|
return l
|
|
}()
|
|
|
|
private(set) lazy var refreshButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "Map/map_btn_menu"), for: .normal)
|
|
btn.contentMode = .scaleAspectFit
|
|
return btn
|
|
}()
|
|
|
|
private(set) lazy var inviteButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setTitle("邀请加入", for: .normal)
|
|
btn.titleLabel?.font = pingFangMedium(11)
|
|
btn.setTitleColor(.white, for: .normal)
|
|
btn.backgroundColor = UIColor.white.withAlphaComponent(0.2)
|
|
btn.layer.cornerRadius = 12
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Member List
|
|
private(set) lazy var memberListScrollView: UIScrollView = {
|
|
let sv = UIScrollView()
|
|
sv.showsVerticalScrollIndicator = false
|
|
sv.alwaysBounceVertical = true
|
|
sv.backgroundColor = .white
|
|
return sv
|
|
}()
|
|
|
|
private lazy var memberListStackView: UIStackView = {
|
|
let sv = UIStackView()
|
|
sv.axis = .vertical
|
|
sv.spacing = 0
|
|
return sv
|
|
}()
|
|
|
|
// MARK: - Init
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
setupUI()
|
|
setupLayout()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - Setup
|
|
private func setupUI() {
|
|
backgroundColor = .white
|
|
|
|
addSubview(topNavBar)
|
|
topNavBar.addSubview(avatarButton)
|
|
topNavBar.addSubview(titleLabel)
|
|
topNavBar.addSubview(goldCircleIcon)
|
|
topNavBar.addSubview(dropdownArrowButton)
|
|
|
|
#if !targetEnvironment(simulator)
|
|
addSubview(mapView)
|
|
#else
|
|
addSubview(mapPlaceholderView)
|
|
#endif
|
|
|
|
addSubview(controlStrip)
|
|
controlStrip.addSubview(sosButton)
|
|
controlStrip.addSubview(checkinButton)
|
|
controlStrip.addSubview(bubbleButton)
|
|
|
|
addSubview(sirenButton)
|
|
addSubview(locationButton)
|
|
addSubview(announcementBar)
|
|
announcementBar.addSubview(announcementLabel)
|
|
announcementBar.addSubview(announcementCloseBtn)
|
|
|
|
addSubview(bottomPanel)
|
|
bottomPanel.addSubview(panelHeaderBg)
|
|
bottomPanel.addSubview(circleIcon)
|
|
bottomPanel.addSubview(memberListTitleLabel)
|
|
bottomPanel.addSubview(memberCountLabel)
|
|
bottomPanel.addSubview(refreshButton)
|
|
bottomPanel.addSubview(inviteButton)
|
|
bottomPanel.addSubview(memberListScrollView)
|
|
memberListScrollView.addSubview(memberListStackView)
|
|
}
|
|
|
|
private func setupLayout() {
|
|
topNavBar.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
topNavBar.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
|
|
topNavBar.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
topNavBar.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
topNavBar.heightAnchor.constraint(equalToConstant: topNavHeight)
|
|
])
|
|
|
|
avatarButton.layoutChain
|
|
.left(12).centerY()
|
|
.size(CGSize(width: 36, height: 36))
|
|
|
|
titleLabel.layoutChain.center()
|
|
|
|
goldCircleIcon.layoutChain
|
|
.leftToRightOfView(titleLabel, offset: 4)
|
|
.centerY(titleLabel)
|
|
.size(CGSize(width: 18, height: 14))
|
|
|
|
dropdownArrowButton.layoutChain
|
|
.right(12).centerY()
|
|
.size(CGSize(width: 20, height: 20))
|
|
|
|
#if !targetEnvironment(simulator)
|
|
mapView.layoutChain
|
|
.topToBottomOfView(topNavBar)
|
|
.left(0).right(0).bottom(0)
|
|
#else
|
|
mapPlaceholderView.layoutChain
|
|
.topToBottomOfView(topNavBar)
|
|
.left(0).right(0).bottom(0)
|
|
#endif
|
|
|
|
// Left control strip
|
|
controlStrip.layoutChain
|
|
.left(8)
|
|
.topToBottomOfView(topNavBar, offset: 12)
|
|
.width(controlStripWidth)
|
|
|
|
sosButton.layoutChain
|
|
.top(10).centerX()
|
|
.left(0).right(0)
|
|
.height(30)
|
|
|
|
checkinButton.layoutChain
|
|
.topToBottomOfView(sosButton, offset: 12)
|
|
.centerX()
|
|
.left(0).right(0)
|
|
.height(30)
|
|
|
|
bubbleButton.layoutChain
|
|
.topToBottomOfView(checkinButton, offset: 12)
|
|
.centerX()
|
|
.left(0).right(0)
|
|
.height(30)
|
|
.bottom(10)
|
|
|
|
// Siren (alarm) button to the left of control strip
|
|
sirenButton.layoutChain
|
|
.leftToRightOfView(controlStrip, offset: 6)
|
|
.centerY(controlStrip)
|
|
.size(CGSize(width: 28, height: 28))
|
|
|
|
// Location button
|
|
locationButton.layoutChain
|
|
.right(12)
|
|
.bottomToTopOfView(bottomPanel, offset: -12)
|
|
.size(CGSize(width: 20, height: 20))
|
|
|
|
// Announcement bar
|
|
announcementBar.layoutChain
|
|
.left(24).right(24)
|
|
.topToBottomOfView(topNavBar, offset: 8)
|
|
.height(22)
|
|
|
|
announcementLabel.layoutChain
|
|
.left(12).centerY()
|
|
|
|
announcementCloseBtn.layoutChain
|
|
.right(8).centerY()
|
|
.size(CGSize(width: 10, height: 10))
|
|
|
|
// Bottom panel
|
|
bottomPanel.layoutChain
|
|
.left(0).right(0).bottom(0)
|
|
|
|
bottomPanel.translatesAutoresizingMaskIntoConstraints = false
|
|
let hc = bottomPanel.heightAnchor.constraint(equalToConstant: bottomPanelHeight(for: bounds.height))
|
|
hc.isActive = true
|
|
bottomPanelHeightConstraint = hc
|
|
|
|
// Panel header
|
|
panelHeaderBg.layoutChain
|
|
.top(0).left(0).right(0)
|
|
.height(panelHeaderHeight)
|
|
|
|
circleIcon.layoutChain
|
|
.left(12).top(12)
|
|
.size(CGSize(width: 26, height: 26))
|
|
|
|
memberListTitleLabel.layoutChain
|
|
.leftToRightOfView(circleIcon, offset: 6)
|
|
.centerY(circleIcon)
|
|
|
|
memberCountLabel.layoutChain
|
|
.leftToRightOfView(circleIcon, offset: 6)
|
|
.topToBottomOfView(memberListTitleLabel, offset: 2)
|
|
|
|
refreshButton.layoutChain
|
|
.rightToLeftOfView(inviteButton, offset: -12)
|
|
.centerY(circleIcon)
|
|
.size(CGSize(width: 16, height: 16))
|
|
|
|
inviteButton.layoutChain
|
|
.right(12).centerY(circleIcon)
|
|
.size(CGSize(width: 64, height: 24))
|
|
|
|
// Member list scroll view
|
|
memberListScrollView.layoutChain
|
|
.topToBottomOfView(panelHeaderBg)
|
|
.left(0).right(0).bottom(0)
|
|
|
|
memberListStackView.layoutChain
|
|
.top(0).left(0)
|
|
.widthToView(memberListScrollView)
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
bottomPanelHeightConstraint?.constant = bottomPanelHeight(for: bounds.height)
|
|
}
|
|
|
|
/// Bottom panel is ~42% of view height, capped between 280-350pt
|
|
private func bottomPanelHeight(for totalHeight: CGFloat) -> CGFloat {
|
|
let ratio: CGFloat = 0.42
|
|
return min(350, max(280, totalHeight * ratio))
|
|
}
|
|
|
|
// MARK: - Configure Member List
|
|
func configureMemberList(with members: [CircleMember], onSelect: @escaping (CircleMember) -> Void) {
|
|
memberListStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
let onlineCount = members.filter { $0.isOnline }.count
|
|
memberCountLabel.text = "共\(members.count)成员 / \(onlineCount)在线"
|
|
|
|
for (index, member) in members.enumerated() {
|
|
let row = MemberRowView(member: member)
|
|
row.tag = index
|
|
let tap = UITapGestureRecognizer(target: self, action: #selector(memberRowTapped(_:)))
|
|
row.addGestureRecognizer(tap)
|
|
memberListStackView.addArrangedSubview(row)
|
|
|
|
if index < members.count - 1 {
|
|
let sep = UIView()
|
|
sep.backgroundColor = UIColor(hexStr: "#F0F0F0")
|
|
memberListStackView.addArrangedSubview(sep)
|
|
sep.layoutChain.height(0.5)
|
|
}
|
|
}
|
|
|
|
let totalContentHeight = CGFloat(members.count) * MemberRowView.rowHeight
|
|
+ CGFloat(max(0, members.count - 1)) * 0.5
|
|
memberListScrollView.contentSize = CGSize(
|
|
width: memberListScrollView.bounds.width,
|
|
height: max(totalContentHeight, memberListScrollView.bounds.height)
|
|
)
|
|
}
|
|
|
|
@objc private func memberRowTapped(_ gesture: UITapGestureRecognizer) {
|
|
// Handled via bindings in ViewController
|
|
}
|
|
}
|
|
|
|
// MARK: - MemberRowView
|
|
final class MemberRowView: UIView {
|
|
|
|
static let rowHeight: CGFloat = 38
|
|
|
|
private let avatarImageView: UIImageView = {
|
|
let iv = UIImageView()
|
|
iv.contentMode = .scaleAspectFill
|
|
iv.layer.cornerRadius = 12.5
|
|
iv.layer.borderWidth = 1
|
|
iv.layer.borderColor = UIColor.white.cgColor
|
|
iv.clipsToBounds = true
|
|
iv.backgroundColor = UIColor(hexStr: "#E0E0E0")
|
|
return iv
|
|
}()
|
|
|
|
private let onlineDot: UIView = {
|
|
let v = UIView()
|
|
v.layer.cornerRadius = 4
|
|
v.layer.borderWidth = 1
|
|
v.layer.borderColor = UIColor.white.cgColor
|
|
return v
|
|
}()
|
|
|
|
private let nameLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = UIFont(name: "PingFangSC-Semibold", size: 12)
|
|
?? UIFont.systemFont(ofSize: 12, weight: .semibold)
|
|
l.textColor = UIColor(hexStr: "#0F2846")
|
|
return l
|
|
}()
|
|
|
|
private let addressLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = UIFont(name: "PingFangSC-Regular", size: 10)
|
|
?? UIFont.systemFont(ofSize: 10)
|
|
l.textColor = UIColor(hexStr: "#8D8D8D")
|
|
return l
|
|
}()
|
|
|
|
private let statusBadge: UIView = {
|
|
let v = UIView()
|
|
v.layer.cornerRadius = 4
|
|
return v
|
|
}()
|
|
|
|
private let statusLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = UIFont(name: "PingFangSC-Medium", size: 9)
|
|
?? UIFont.systemFont(ofSize: 9, weight: .medium)
|
|
l.textAlignment = .center
|
|
return l
|
|
}()
|
|
|
|
private let timeLabel: UILabel = {
|
|
let l = UILabel()
|
|
l.font = UIFont(name: "PingFangSC-Regular", size: 10)
|
|
?? UIFont.systemFont(ofSize: 10)
|
|
l.textColor = UIColor(hexStr: "#999999")
|
|
return l
|
|
}()
|
|
|
|
private lazy var navButton: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "Map/map_current_location"), for: .normal)
|
|
btn.contentMode = .scaleAspectFit
|
|
return btn
|
|
}()
|
|
|
|
let member: CircleMember
|
|
|
|
init(member: CircleMember) {
|
|
self.member = member
|
|
super.init(frame: .zero)
|
|
setupUI()
|
|
configure()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupUI() {
|
|
addSubview(avatarImageView)
|
|
addSubview(onlineDot)
|
|
addSubview(nameLabel)
|
|
addSubview(addressLabel)
|
|
addSubview(statusBadge)
|
|
statusBadge.addSubview(statusLabel)
|
|
addSubview(timeLabel)
|
|
addSubview(navButton)
|
|
|
|
avatarImageView.layoutChain
|
|
.left(12).centerY()
|
|
.size(CGSize(width: 25, height: 25))
|
|
|
|
onlineDot.layoutChain
|
|
.rightToView(avatarImageView, offset: 2)
|
|
.bottomToView(avatarImageView, offset: 2)
|
|
.size(CGSize(width: 8, height: 8))
|
|
|
|
nameLabel.layoutChain
|
|
.leftToRightOfView(avatarImageView, offset: 8)
|
|
.top(6)
|
|
|
|
addressLabel.layoutChain
|
|
.leftToRightOfView(avatarImageView, offset: 8)
|
|
.topToBottomOfView(nameLabel, offset: 2)
|
|
|
|
statusBadge.layoutChain
|
|
.leftToRightOfView(nameLabel, offset: 6)
|
|
.centerY(nameLabel)
|
|
.height(16)
|
|
|
|
statusLabel.layoutChain
|
|
.left(6).right(6).centerY()
|
|
|
|
timeLabel.layoutChain
|
|
.rightToLeftOfView(navButton, offset: -4)
|
|
.centerY()
|
|
|
|
navButton.layoutChain
|
|
.right(12).centerY()
|
|
.size(CGSize(width: 15, height: 15))
|
|
|
|
self.layoutChain.height(Self.rowHeight)
|
|
}
|
|
|
|
private func configure() {
|
|
avatarImageView.image = UIImage(named: member.avatar)
|
|
|
|
if member.isOnline {
|
|
onlineDot.backgroundColor = UIColor(hexStr: "#4CD964")
|
|
} else {
|
|
onlineDot.backgroundColor = UIColor(hexStr: "#D4D4D4")
|
|
}
|
|
onlineDot.isHidden = false
|
|
|
|
nameLabel.text = member.name
|
|
addressLabel.text = "在 \(member.address)"
|
|
|
|
if member.isOwner {
|
|
statusBadge.backgroundColor = UIColor(hexStr: "#9FD9FF")
|
|
statusLabel.text = "圈主"
|
|
statusLabel.textColor = UIColor(hexStr: "#194045")
|
|
} else if member.isOnline {
|
|
statusBadge.backgroundColor = UIColor(hexStr: "#9FD9FF")
|
|
statusLabel.text = "在线"
|
|
statusLabel.textColor = UIColor(hexStr: "#194045")
|
|
} else {
|
|
statusBadge.backgroundColor = UIColor(hexStr: "#EDEDED")
|
|
statusLabel.text = "离线"
|
|
statusLabel.textColor = UIColor(hexStr: "#999999")
|
|
}
|
|
|
|
timeLabel.text = member.lastUpdateText
|
|
|
|
backgroundColor = member.isCurrentUser ? UIColor(hexStr: "#EFF9FF") : .clear
|
|
}
|
|
}
|