jsdw_ios/QuickLocation/Section/Group/GroupView.swift

775 lines
24 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// GroupView.swift
// QuickLocation
//
// Created by on 2026/5/29.
//
import UIKit
import RxSwift
import RxCocoa
import SDCycleScrollView
import OpenIMSDK
import SwiftDate
// MARK: - PanScrollView PanScrollView
class PanScrollView: UIScrollView {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
class GroupView: UIView {
var disposeBag = DisposeBag()
/// 0 = 1 =
private(set) var currentTopTab: Int = 0
///
private var isSticky = false
/// tableview
private var isSubCanScroll = false
var stickThreshold: CGFloat {
// segment listContainer listContainer
return listContainer.dl.y
}
// MARK: - Setup
private func setupUI() {
addSubview(navBgView)
addSubview(topBar)
topBar.addSubview(circleTabLabel)
topBar.addSubview(itineraryTabLabel)
topBar.addSubview(messageBtn)
addSubview(circlePage)
circlePage.addSubview(mainScrollView)
mainScrollView.addSubview(contentView)
contentView.addSubview(actionButtonsView)
actionButtonsView.addSubview(createGroupBtn)
actionButtonsView.addSubview(joinGroupBtn)
contentView.addSubview(listContainer)
listContainer.addSubview(segmentView)
segmentView.addSubview(joinedTabLabel)
segmentView.addSubview(createdTabLabel)
segmentView.addSubview(tabIndicator)
listContainer.addSubview(segmentScrollView)
segmentScrollView.addSubview(segmentContentView)
// (joined) | (created)
segmentContentView.addSubview(joinedTableView)
segmentContentView.addSubview(createdTableView)
// /
contentView.addSubview(cycleScrollView)
contentView.addSubview(hotGroupTitleLabel)
contentView.addSubview(hotGroupsCollectionView)
cycleScrollView.isHidden = true
hotGroupTitleLabel.isHidden = true
hotGroupsCollectionView.isHidden = true
addSubview(itineraryPage)
itineraryPage.isHidden = true
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
topBar.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
circleTabLabel.layoutChain
.left(20)
.bottom(10)
.height(28)
itineraryTabLabel.layoutChain
.leftToRightOfView(circleTabLabel, offset: 20)
.centerY(circleTabLabel)
.height(28)
messageBtn.layoutChain
.right(16)
.centerY(circleTabLabel)
.width(28)
.height(28)
circlePage.layoutChain
.topToBottomOfView(topBar)
.edges(excludingEdge: .top)
itineraryPage.layoutChain
.topToBottomOfView(topBar)
.edges(excludingEdge: .top)
mainScrollView.layoutChain
.edges()
contentView.layoutChain
.edges()
.widthToView(mainScrollView)
actionButtonsView.layoutChain
.top(12)
.edgesHorzontal(16)
.height(108)
createGroupBtn.layoutChain
.left()
.edgesVertical()
.widthToView(joinGroupBtn)
joinGroupBtn.layoutChain
.topToView(createGroupBtn)
.bottomToView(createGroupBtn)
.leftToRightOfView(createGroupBtn, offset: 12)
.right()
.widthToView(createGroupBtn)
listContainer.layoutChain
.topToBottomOfView(actionButtonsView, offset: 16)
.edgesHorzontal()
.bottom()
let labelWidth = (UIScreen.main.bounds.width - 60) / 2
segmentView.layoutChain
.top()
.edgesHorzontal()
.height(44)
joinedTabLabel.layoutChain
.left(30).centerY()
.width(labelWidth)
createdTabLabel.layoutChain
.right(30).centerY()
.width(labelWidth)
tabIndicator.layoutChain
.topToBottomOfView(joinedTabLabel, offset: 4)
.centerX(joinedTabLabel)
.width(19).height(4)
segmentScrollView.layoutChain
.topToBottomOfView(segmentView)
.edgesHorzontal()
.bottom()
.height(kScreenHeight - kNaviHeight - 44 - 12 - 108 - 16)
segmentContentView.layoutChain
.edges()
.heightToView(segmentScrollView)
let svWidth = UIScreen.main.bounds.width
joinedTableView.layoutChain
.top().bottom().left()
.width(svWidth)
createdTableView.layoutChain
.top().bottom()
.leftToRightOfView(joinedTableView)
.width(svWidth)
//
cycleScrollView.layoutChain
.top(0).left(0).width(0).height(0)
hotGroupTitleLabel.layoutChain
.top(0).left(0).width(0).height(0)
hotGroupsCollectionView.layoutChain
.top(0).left(0).width(0).height(0)
configureActionCard(
createGroupBtn,
bgColor: UIColor(hexStr: "#D6F3FF"),
title: "创建圈子",
subtitle: "和朋友一起定位玩"
)
configureActionCard(
joinGroupBtn,
bgColor: UIColor(hexStr: "#E8F8C8"),
title: "加入圈子",
subtitle: "输入邀请码快速加入"
)
}
private func configureActionCard(_ card: UIView, bgColor: UIColor, title: String, subtitle: String) {
card.backgroundColor = bgColor
card.cornerRadius = 16
card.clipsToBounds = true
let titleLab = UILabel()
titleLab.text = title
titleLab.font = .systemFont(ofSize: 16, weight: .bold)
titleLab.textColor = UIColor(hexStr: "#0F2846")
let subLab = UILabel()
subLab.text = subtitle
subLab.font = .systemFont(ofSize: 11, weight: .medium)
subLab.textColor = UIColor(hexStr: "#6B7A90")
let cta = UILabel()
cta.text = "立即体验"
cta.font = .systemFont(ofSize: 12, weight: .semibold)
cta.textColor = UIColor(hexStr: "#0F2846")
cta.textAlignment = .center
cta.backgroundColor = .white
cta.cornerRadius = 12
cta.clipsToBounds = true
card.addSubview(titleLab)
card.addSubview(subLab)
card.addSubview(cta)
titleLab.layoutChain
.top(16)
.left(14)
.right(14)
subLab.layoutChain
.topToBottomOfView(titleLab, offset: 6)
.left(14)
.right(14)
cta.layoutChain
.left(14)
.bottom(14)
.width(72)
.height(24)
}
override func layoutSubviews() {
super.layoutSubviews()
segmentContentView.layoutChain.width(bounds.width * 2)
listContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
listContainer.layer.cornerRadius = 20
listContainer.clipsToBounds = true
}
// MARK: - Nav
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_1")
iv.contentMode = .scaleAspectFill
return iv
}()
// MARK: - Top Bar
lazy var topBar: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
lazy var circleTabLabel: UILabel = {
let label = UILabel()
label.text = "圈子"
label.font = FontManager.youSheBiaoTiHei(32)
label.textColor = UIColor(hexStr: "#0F2846")
label.isUserInteractionEnabled = true
return label
}()
lazy var itineraryTabLabel: UILabel = {
let label = UILabel()
label.text = "行程"
label.font = FontManager.youSheBiaoTiHei(24)
label.textColor = UIColor(hexStr: "#0F2846").withAlphaComponent(0.45)
label.isUserInteractionEnabled = true
return label
}()
lazy var messageBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Home/message"), for: .normal)
btn.isHidden = true
return btn
}()
lazy var navView: BaseNavigationView = {
let nav = BaseNavigationView(title: "圈子")
nav.isHidden = true
return nav
}()
lazy var scanBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Group/scan"), for: .normal)
btn.isHidden = true
return btn
}()
// MARK: - Pages
lazy var circlePage: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
lazy var itineraryPage: GroupItineraryView = {
let v = GroupItineraryView()
return v
}()
// MARK: - Main Scroll
lazy var mainScrollView: PanScrollView = {
let sv = PanScrollView()
sv.backgroundColor = .clear
sv.delegate = self
sv.showsVerticalScrollIndicator = false
sv.bounces = false
return sv
}()
lazy var contentView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
// MARK: -
lazy var cycleScrollView: SDCycleScrollView = {
let view = SDCycleScrollView(frame: .zero)
view.backgroundColor = .lightGray
view.scrollDirection = .horizontal
view.showPageControl = true
view.bannerImageViewContentMode = .scaleAspectFill
view.autoScrollTimeInterval = 5
view.currentPageDotColor = UIColor(hexStr: "#16B3FF")
view.pageDotColor = UIColor(hexStr: "#7AD6FF").withAlphaComponent(0.4)
view.pageControlDotSize = CGSize(width: 8, height: 8)
view.clipsToBounds = false
view.pageControlBottomOffset = -34
return view
}()
// MARK: - /
private lazy var actionButtonsView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
lazy var createGroupBtn: UIView = {
let v = UIView()
v.isUserInteractionEnabled = true
return v
}()
lazy var joinGroupBtn: UIView = {
let v = UIView()
v.isUserInteractionEnabled = true
return v
}()
// MARK: -
private lazy var hotGroupTitleLabel: UILabel = {
let label = UILabel()
label.text = "热门酷圈"
label.font = .systemFont(ofSize: 16, weight: .semibold)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
private(set) lazy var hotGroupsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 75, height: 88)
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.showsHorizontalScrollIndicator = false
cv.register(HotGroupCell.self, forCellWithReuseIdentifier: HotGroupCell.reuseId)
return cv
}()
// MARK: - List container
private lazy var listContainer: UIView = {
let v = UIView()
v.backgroundColor = .white
return v
}()
// MARK: - Segment0 =joined1 =created
private(set) lazy var segmentView: UIView = {
let v = UIView()
v.backgroundColor = .white
let line = UIView()
line.backgroundColor = UIColor(hexStr: "#B5B5B5").withAlphaComponent(0.12)
// v.addSubview(line)
// line.layoutChain
// .edgesHorzontal()
// .height(1)
// .bottom(4)
return v
}()
/// joined index 0
private(set) lazy var joinedTabLabel: UILabel = {
let label = UILabel()
label.text = "全部圈子"
label.font = .systemFont(ofSize: 16, weight: .heavy)
label.textColor = UIColor(hexStr: "#2A3648")
label.textAlignment = .center
label.isUserInteractionEnabled = true
return label
}()
/// created index 1
private(set) lazy var createdTabLabel: UILabel = {
let label = UILabel()
label.text = "我的圈子"
label.font = .systemFont(ofSize: 16, weight: .medium)
label.textColor = UIColor(hexStr: "#2A3648")
label.textAlignment = .center
label.isUserInteractionEnabled = true
return label
}()
private lazy var tabIndicator: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#85D8FF")
v.layer.cornerRadius = 2
return v
}()
// MARK: - (PanScrollView)
private(set) lazy var segmentScrollView: PanScrollView = {
let sv = PanScrollView()
sv.isPagingEnabled = true
sv.showsHorizontalScrollIndicator = false
sv.bounces = false
sv.delegate = self
return sv
}()
private lazy var segmentContentView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
// MARK: -
private(set) lazy var createdTableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.estimatedRowHeight = 70
tv.bounces = true
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
return tv
}()
private(set) lazy var joinedTableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.estimatedRowHeight = 70
tv.bounces = true
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
return tv
}()
// MARK: - Segment
var currentSegmentIndex: Int = 0
/// index: 0=(joined)1=(created)
func selectSegment(at index: Int) {
currentSegmentIndex = index
let isAll = index == 0
UIView.animate(withDuration: 0.2) {
self.joinedTabLabel.textColor = isAll ? UIColor(hexStr: "#2A3648") : UIColor(hexStr: "#2A3648", alpha: 0.5)
self.joinedTabLabel.font = .systemFont(ofSize: 16, weight: isAll ? .heavy : .medium)
self.createdTabLabel.textColor = isAll ? UIColor(hexStr: "#2A3648", alpha: 0.5) : UIColor(hexStr: "#2A3648")
self.createdTabLabel.font = .systemFont(ofSize: 16, weight: isAll ? .medium : .heavy)
self.tabIndicator.center.x = isAll
? self.joinedTabLabel.center.x
: self.createdTabLabel.center.x
self.layoutIfNeeded()
}
}
func selectTopTab(at index: Int) {
guard index == 0 || index == 1 else { return }
currentTopTab = index
let isCircle = index == 0
circlePage.isHidden = !isCircle
itineraryPage.isHidden = isCircle
circleTabLabel.font = FontManager.youSheBiaoTiHei(isCircle ? 32 : 24)
itineraryTabLabel.font = FontManager.youSheBiaoTiHei(isCircle ? 24 : 32)
circleTabLabel.textColor = isCircle ? UIColor(hexStr: "#353B4F") : UIColor(hexStr: "#2A3648", alpha: 0.5)
itineraryTabLabel.textColor = isCircle ? UIColor(hexStr: "#2A3648", alpha: 0.5) : UIColor(hexStr: "#353B4F")
}
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = UIColor(hexStr: "#FAFAFA")
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - UIScrollViewDelegate PanView + PanSubItemView
extension GroupView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
handleMainScroll(scrollView)
} else if scrollView == segmentScrollView {
let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
if page != currentSegmentIndex, page >= 0, page < 2 {
currentSegmentIndex = page
selectSegment(at: page)
}
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
guard scrollView != mainScrollView, scrollView != segmentScrollView else { return }
if scrollView.contentOffset.y > 0 {
isSubCanScroll = true
}
}
/// GroupViewController rx.didScroll
func handleTableViewScroll(_ scrollView: UIScrollView) {
if isSubCanScroll {
if scrollView.contentOffset.y <= 0 {
isSubCanScroll = false
isSticky = false
scrollView.contentOffset.y = 0
}
} else {
scrollView.contentOffset.y = 0
}
}
// MARK: - scroll
private func handleMainScroll(_ scrollView: UIScrollView) {
if isSubCanScroll {
scrollView.contentOffset.y = stickThreshold
isSticky = true
selectSegment(at: currentSegmentIndex)
} else {
let offsetY = scrollView.contentOffset.y
if offsetY >= stickThreshold {
scrollView.contentOffset.y = stickThreshold
isSticky = true
isSubCanScroll = true
selectSegment(at: currentSegmentIndex)
}
}
}
}
// MARK: - HotGroupCell
final class HotGroupCell: UICollectionViewCell {
static let reuseId = "HotGroupCell"
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 10
iv.clipsToBounds = true
iv.backgroundColor = .clear
return iv
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
label.textAlignment = .center
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = ThemeManager.shared.color.contentColor
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(iconView)
contentView.addSubview(nameLabel)
contentView.addSubview(countLabel)
iconView.layoutChain
.top().centerX()
.width(50).height(50)
nameLabel.layoutChain
.topToBottomOfView(iconView, offset: 6)
.edgesHorzontal()
countLabel.layoutChain
.topToBottomOfView(nameLabel, offset: 2)
.edgesHorzontal()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ model: GroupInfoModel) {
iconView.image = model.groupIcon
nameLabel.text = model.name
countLabel.text = "\(model.people_no)个成员"
}
}
// MARK: - CircleGroupCell
final class CircleGroupCell: UITableViewCell {
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.cornerRadius = 20
iv.clipsToBounds = true
iv.backgroundColor = UIColor(hexStr: "#F0F0F0")
return iv
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
return label
}()
lazy var unreadBadgeView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#FF383C")
view.cornerRadius = 7
view.isHidden = true
return view
}()
private let unreadBadge: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = .white
return label
}()
private let timeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .regular)
label.textColor = UIColor(hexStr: "#999999")
label.textAlignment = .right
return label
}()
private let dividerLine: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#F0F0F0")
return v
}()
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .white
contentView.addSubview(iconView)
contentView.addSubview(nameLabel)
contentView.addSubview(countLabel)
contentView.addSubview(unreadBadgeView)
unreadBadgeView.addSubview(unreadBadge)
contentView.addSubview(timeLabel)
contentView.addSubview(dividerLine)
iconView.layoutChain
.left(15).edgesVertical(15)
.width(40).height(40)
nameLabel.layoutChain
.topToView(iconView, offset: 4)
.leftToRightOfView(iconView, offset: 12)
.right(90)
countLabel.layoutChain
.topToBottomOfView(nameLabel, offset: 4)
.leftToView(nameLabel)
unreadBadgeView.layoutChain
.centerY(nameLabel)
.right(15)
unreadBadge.layoutChain
.edgesHorzontal(8)
.edgesVertical(1)
timeLabel.layoutChain
.right(15)
.centerY(countLabel)
dividerLine.layoutChain
.edgesHorzontal(15)
.bottom()
.height(0.5)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ model: GroupCellData) {
let g = model.groupInfo
iconView.image = model.localIcon
nameLabel.text = g.groupName ?? ""
countLabel.text = "\(g.memberCount)个成员"
//
if model.unreadCount > 0 {
unreadBadgeView.isHidden = false
unreadBadge.text = model.unreadCount > 99 ? "99+" : "\(model.unreadCount)"
} else {
unreadBadgeView.isHidden = true
}
//
if model.lastMsgTime > 0 {
let date = Date(timeIntervalSince1970: TimeInterval(model.lastMsgTime) / 1000)
timeLabel.text = date.formatForIM()
} else {
timeLabel.text = ""
}
}
}
// MARK: - Date IM format
private extension Date {
func formatForIM() -> String {
if isToday { return toFormat("HH:mm") }
if isYesterday { return "昨天" }
if Calendar.current.isDate(self, equalTo: Date(), toGranularity: .year) { return toFormat("MM/dd") }
return toFormat("yyyy/MM/dd")
}
}