jsdw_ios/QuickLocation/Section/Group/GroupItineraryView.swift

435 lines
14 KiB
Swift

//
// GroupItineraryView.swift
// QuickLocation
//
import UIKit
import RxSwift
import RxCocoa
import SwiftDate
/// Tab
final class GroupItineraryView: UIView {
var disposeBag = DisposeBag()
var onSwitchGroup: (() -> Void)?
var onDeleteSchedule: ((ScheduleModel) -> Void)?
var onSelectSchedule: ((ScheduleModel) -> Void)?
func updateGroupName(_ name: String) {
groupNameLab.text = name.isEmpty ? " " : name
}
func reloadSchedules(_ list: [ScheduleModel]) {
scheduleSections = Self.groupByDay(list)
tableView.reloadData()
emptyLab.isHidden = !list.isEmpty
}
private var scheduleSections: [(dayText: String, dateText: String, items: [ScheduleModel])] = []
private func setupUI() {
addSubview(headerRow)
headerRow.addSubview(groupIcon)
headerRow.addSubview(groupNameLab)
headerRow.addSubview(changeGroupBtn)
addSubview(tableView)
addSubview(emptyLab)
addSubview(createBtn)
headerRow.layoutChain
.top(8)
.edgesHorzontal(16)
.height(28)
groupIcon.layoutChain
.left()
.centerY()
.width(22)
.height(22)
changeGroupBtn.layoutChain
.right()
.centerY()
.height(28)
groupNameLab.layoutChain
.leftToRightOfView(groupIcon, offset: 8)
.centerY()
.rightToLeftOfView(changeGroupBtn, offset: -8, relation: .lessThanOrEqual)
tableView.layoutChain
.topToBottomOfView(headerRow, offset: 12)
.edgesHorzontal()
.bottom()
emptyLab.layoutChain
.centerX()
.centerY(tableView)
createBtn.layoutChain
.bottom(kSafeBottomMargin + 102)
.right(10)
.width(60)
.heightToWidth(1)
}
private func setupRx() {
createBtn.rx.tap.subscribe(onNext: { _ in
AppRouter.push(Route.createSchedule)
}).disposed(by: disposeBag)
changeGroupBtn.rx.tap
.subscribe(onNext: { [weak self] in self?.onSwitchGroup?() })
.disposed(by: disposeBag)
}
private static func groupByDay(_ list: [ScheduleModel]) -> [(dayText: String, dateText: String, items: [ScheduleModel])] {
let sorted = list.sorted { $0.timestamp > $1.timestamp }
var map: [String: [ScheduleModel]] = [:]
var order: [String] = []
var dayDateMap: [String: (dayText: String, dateText: String)] = [:]
let fmt = DateFormatter()
fmt.locale = Locale(identifier: "zh_CN")
let calendar = Calendar.current
for item in sorted {
let date = Date(timeIntervalSince1970: TimeInterval(item.timestamp) / 1000)
fmt.dateFormat = "M月d日"
let dateText = fmt.string(from: date)
let dayText: String
if calendar.isDateInToday(date) {
dayText = "今天"
} else if calendar.isDateInYesterday(date) {
dayText = "昨天"
} else {
fmt.dateFormat = "EEEE"
dayText = fmt.string(from: date)
}
let key = "\(dayText) \(dateText)"
if map[key] == nil {
order.append(key)
dayDateMap[key] = (dayText, dateText)
map[key] = []
}
map[key]?.append(item)
}
return order.map { key in
let pair = dayDateMap[key] ?? ("", "")
return (pair.dayText, pair.dateText, map[key] ?? [])
}
}
lazy var headerRow: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
lazy var groupIcon: UIImageView = {
let iv = UIImageView(image: UIImage(named: "Home/group_name_icon"))
iv.contentMode = .scaleAspectFit
return iv
}()
lazy var groupNameLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#353B4F")
label.text = " "
return label
}()
lazy var changeGroupBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("切换圈子 ", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#353B4F"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
btn.setImage(UIImage(named: "Common/arrow_right"), for: .normal)
btn.semanticContentAttribute = .forceRightToLeft
return btn
}()
lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero, style: .grouped)
tv.backgroundColor = UIColor(hexStr: "#FAFAFA")
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(GroupItineraryCell.self)
tv.dataSource = self
tv.delegate = self
tv.estimatedRowHeight = 100
tv.rowHeight = UITableView.automaticDimension
tv.sectionHeaderHeight = 36
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
if #available(iOS 15.0, *) {
tv.sectionHeaderTopPadding = 0
}
return tv
}()
lazy var emptyLab: UILabel = {
let label = UILabel()
label.text = "暂无行程"
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
label.isHidden = true
return label
}()
lazy var createBtn: UIButton = {
let btn = UIButton()
btn.setImage(UIImage(named: "Schedule/create"), for: .normal)
return btn
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
setupUI()
setupRx()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - UITableView
extension GroupItineraryView: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
scheduleSections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
scheduleSections[section].items.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = UIView()
header.backgroundColor = .clear
let sectionData = scheduleSections[section]
let label = UILabel()
let color = UIColor(hexStr: "#353B4F")
let attr = NSMutableAttributedString()
attr.append(NSAttributedString(
string: sectionData.dayText,
attributes: [
.font: UIFont.systemFont(ofSize: 16, weight: .bold),
.foregroundColor: color
]
))
if !sectionData.dateText.isEmpty {
attr.append(NSAttributedString(
string: " \(sectionData.dateText)",
attributes: [
.font: UIFont.systemFont(ofSize: 13, weight: .medium),
.foregroundColor: color
]
))
}
label.attributedText = attr
header.addSubview(label)
label.layoutChain.left(16).centerY()
return header
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
.leastNormalMagnitude
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
UIView()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: GroupItineraryCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(scheduleSections[indexPath.section].items[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
onSelectSchedule?(scheduleSections[indexPath.section].items[indexPath.row])
}
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let model = scheduleSections[indexPath.section].items[indexPath.row]
let delete = UIContextualAction(style: .destructive, title: nil) { [weak self] _, _, done in
self?.onDeleteSchedule?(model)
done(true)
}
delete.image = UIImage(systemName: "trash")
delete.backgroundColor = UIColor(hexStr: "#FF5A5F")
return UISwipeActionsConfiguration(actions: [delete])
}
}
// MARK: - GroupItineraryCell
final class GroupItineraryCell: UITableViewCell {
func configure(_ model: ScheduleModel) {
avatarImg.image = model.userIcon
nameLab.text = model.nick_name
let count = max(model.points.count, 0)
placeCountLab.text = "\(count)"
let date = Date(timeIntervalSince1970: TimeInterval(model.timestamp) / 1000)
let ongoing = Calendar.current.isDateInToday(date) || date > Date()
statusLab.text = ongoing ? "进行中" : "已完成"
statusLab.textColor = ongoing ? UIColor(hexStr: "#00B0C6") : UIColor(hexStr: "#999999")
statusLab.backgroundColor = ongoing
? UIColor(hexStr: "#DCFCFF")
: UIColor(hexStr: "#F0F0F0")
let tag = model.groups.first?.group_name ?? ""
tagLab.text = tag.isEmpty ? nil : " \(tag) "
tagLab.isHidden = tag.isEmpty
setNeedsLayout()
}
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
contentView.addSubview(cardView)
cardView.addSubview(avatarImg)
cardView.addSubview(nameLab)
cardView.addSubview(statusLab)
cardView.addSubview(summaryLab)
cardView.addSubview(placeCountLab)
cardView.addSubview(summaryTailLab)
cardView.addSubview(tagLab)
cardView.layoutChain
.edgesVertical(6)
.edgesHorzontal(16)
avatarImg.layoutChain
.top(14)
.left(14)
.width(36)
.height(36)
nameLab.layoutChain
.leftToRightOfView(avatarImg, offset: 10)
.centerY(avatarImg)
statusLab.layoutChain
.right(14)
.centerY(avatarImg)
.height(22)
summaryLab.layoutChain
.topToBottomOfView(avatarImg, offset: 12)
.left(14)
placeCountLab.layoutChain
.leftToRightOfView(summaryLab, offset: 4)
.centerY(summaryLab)
.width(22)
.height(22)
summaryTailLab.layoutChain
.leftToRightOfView(placeCountLab, offset: 4)
.centerY(summaryLab)
tagLab.layoutChain
.topToBottomOfView(summaryLab, offset: 12)
.right(14)
.bottom(14)
.height(20)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var cardView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.cornerRadius = 16
return v
}()
private lazy var avatarImg: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.cornerRadius = 10
iv.clipsToBounds = true
return iv
}()
private lazy var nameLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(hexStr: "#353B4F")
return label
}()
private lazy var statusLab: PaddingLabel = {
let label = PaddingLabel()
label.font = .systemFont(ofSize: 12, weight: .medium)
label.textAlignment = .center
label.cornerRadius = 4
label.clipsToBounds = true
label.insets = UIEdgeInsets(top: 2, left: 8, bottom: 2, right: 8)
label.borderWidth = 1
label.borderColor = UIColor(hexStr: "#84E2ED")
return label
}()
private lazy var summaryLab: UILabel = {
let label = UILabel()
label.text = "今天停留了"
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#353B4F")
return label
}()
private lazy var placeCountLab: UILabel = {
let label = UILabel()
label.font = FontManager.youSheBiaoTiHei(14)
label.textColor = UIColor(hexStr: "#58EDFF")
label.textAlignment = .center
label.backgroundColor = UIColor(hexStr: "#293445")
label.cornerRadius = 6
label.clipsToBounds = true
return label
}()
private lazy var summaryTailLab: UILabel = {
let label = UILabel()
label.text = "个地方"
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#353B4F")
return label
}()
private lazy var tagLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#FF8A3D")
label.backgroundColor = UIColor(hexStr: "#FFE8D6")
label.cornerRadius = 4
label.clipsToBounds = true
label.textAlignment = .center
return label
}()
}
private final class PaddingLabel: UILabel {
var insets = UIEdgeInsets.zero
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + insets.left + insets.right,
height: size.height + insets.top + insets.bottom)
}
}