// // LockDistractView.swift // QuickLocation // import UIKit import RxSwift import RxCocoa final class LockDistractView: UIView { var disposeBag = DisposeBag() let wallpaperNames = [ "LockDistract/wallpaper_1", "LockDistract/wallpaper_2", "LockDistract/wallpaper_3" ] private(set) var selectedWallpaperIndex = 0 private let copyMaxLength = 20 // MARK: - Init override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor(hexStr: "#FAFAFA") setupUI() setupCopyLimit() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Public func configureMember(name: String, avatar: UIImage?) { memberNameLab.text = name.isEmpty ? " " : name memberAvatar.image = avatar ?? UIImage(named: "Common/default_avatar") } func setTodayLockCount(_ count: Int) { todayCountLab.text = "\(count)" } // MARK: - UI private func setupUI() { addSubview(navBgView) addSubview(navView) navView.addRightButton(switchGroupBtn) addSubview(scrollView) scrollView.addSubview(contentView) addSubview(lockBtn) contentView.addSubview(titleLab) contentView.addSubview(subtitleLab) contentView.addSubview(heroPlaceholder) contentView.addSubview(statsRow) statsRow.addArrangedSubview(memberCard) statsRow.addArrangedSubview(todayCard) contentView.addSubview(appsSectionTitle) contentView.addSubview(appsCard) appsCard.addSubview(appsHeaderRow) appsCard.addSubview(unknownAppIcon) contentView.addSubview(styleSectionTitle) contentView.addSubview(styleCard) addSubview(tipRow) navBgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(160/375) navView.layoutChain .edges(excludingEdge: .bottom) .height(kNaviHeight) lockBtn.layoutChain .bottom(kSafeBottomMargin + 0) .edgesHorzontal(30) .height(56) scrollView.layoutChain .topToBottomOfView(navView) .edgesHorzontal() .bottomToTopOfView(tipRow, offset: -12) contentView.layoutChain .edges() .widthToView(scrollView) titleLab.layoutChain .top(8) .left(20) subtitleLab.layoutChain .topToBottomOfView(titleLab, offset: 6) .left(20) .right(140) heroPlaceholder.layoutChain .top(0) .right(16) .width(120) .height(100) statsRow.layoutChain .topToBottomOfView(subtitleLab, offset: 20) .edgesHorzontal(16) .height(120) appsSectionTitle.layoutChain .topToBottomOfView(statsRow, offset: 22) .left(20) appsCard.layoutChain .topToBottomOfView(appsSectionTitle, offset: 12) .edgesHorzontal(16) appsHeaderRow.layoutChain .top(16) .edgesHorzontal(14) unknownAppIcon.layoutChain .topToBottomOfView(appsHeaderRow, offset: 14) .left(14) .width(56) .height(56) .bottom(16) styleSectionTitle.layoutChain .topToBottomOfView(appsCard, offset: 22) .left(20) styleCard.layoutChain .topToBottomOfView(styleSectionTitle, offset: 12) .edgesHorzontal(16) .bottom(20) tipRow.layoutChain .centerX() .bottomToTopOfView(lockBtn, offset: -6) } private func setupCopyLimit() { copyTF.rx.text.orEmpty .subscribe(onNext: { [weak self] text in guard let self else { return } if text.count > self.copyMaxLength { self.copyTF.text = String(text.prefix(self.copyMaxLength)) } let count = self.copyTF.text?.count ?? 0 self.copyCountLab.text = "\(count)/\(self.copyMaxLength)" }) .disposed(by: disposeBag) } // MARK: - Views lazy var navBgView: UIImageView = { let iv = UIImageView() iv.image = UIImage(named: "Common/navBar_bg_2") iv.contentMode = .scaleAspectFill return iv }() lazy var navView: BaseNavigationView = { BaseNavigationView(title: "") }() lazy var switchGroupBtn: UIButton = { let btn = UIButton(type: .custom) btn.backgroundColor = .white.withAlphaComponent(0.9) btn.setTitle(" 切换圈子 ", for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) btn.setTitleColor(UIColor(hexStr: "#293445"), for: .normal) btn.cornerRadius = 8 btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 12, bottom: 12, right: 4) return btn }() lazy var scrollView: UIScrollView = { let sv = UIScrollView() sv.showsVerticalScrollIndicator = false sv.alwaysBounceVertical = true sv.keyboardDismissMode = .onDrag return sv }() lazy var contentView = UIView() lazy var titleLab: UILabel = { let lab = UILabel() lab.text = "锁住分心" lab.font = FontManager.boboBold(30) lab.textColor = UIColor(hexStr: "#293445") return lab }() lazy var subtitleLab: UILabel = { let lab = UILabel() lab.text = "减少干扰,只专注重要的事" lab.font = .systemFont(ofSize: 13, weight: .medium) lab.textColor = UIColor(hexStr: "#8A94A6") lab.numberOfLines = 2 return lab }() lazy var heroPlaceholder: UIView = { let view = UIView() view.backgroundColor = .clear return view }() lazy var statsRow: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.spacing = 12 stack.distribution = .fillEqually return stack }() lazy var memberCard: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 16 view.addSubview(memberPrevBtn) view.addSubview(memberAvatar) view.addSubview(memberNextBtn) view.addSubview(memberNameChip) memberNameChip.addSubview(memberNameLab) memberAvatar.layoutChain .top(18) .centerX() .width(56) .height(56) memberPrevBtn.layoutChain .centerY(memberAvatar) .left(8) .width(28) .height(28) memberNextBtn.layoutChain .centerY(memberAvatar) .right(8) .width(28) .height(28) memberNameChip.layoutChain .topToBottomOfView(memberAvatar, offset: 10) .centerX() .left(8, relation: .greaterThanOrEqual) .right(8, relation: .greaterThanOrEqual) .bottom(14) memberNameLab.layoutChain .top(6) .bottom(6) .left(5) .right(5) return view }() lazy var memberAvatar: UIImageView = { let iv = UIImageView(image: UIImage(named: "Common/default_avatar")) iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.cornerRadius = 16 return iv }() lazy var memberNameChip: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F2F2F2") view.cornerRadius = 8 return view }() lazy var memberNameLab: UILabel = { let lab = UILabel() lab.text = " " lab.font = .systemFont(ofSize: 10, weight: .bold) lab.textColor = UIColor(hexStr: "#293445") lab.textAlignment = .center lab.lineBreakMode = .byTruncatingTail return lab }() lazy var memberPrevBtn: UIButton = { let btn = UIButton(type: .system) btn.setImage(UIImage(systemName: "chevron.left"), for: .normal) btn.tintColor = UIColor(hexStr: "#9CA3AF") return btn }() lazy var memberNextBtn: UIButton = { let btn = UIButton(type: .system) btn.setImage(UIImage(systemName: "chevron.right"), for: .normal) btn.tintColor = UIColor(hexStr: "#9CA3AF") return btn }() lazy var todayCard: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 16 let title = UILabel() title.text = "今日锁定应用" title.font = .systemFont(ofSize: 16, weight: .bold) title.textColor = UIColor(hexStr: "#293445") view.addSubview(title) title.layoutChain.top(21).left(23) view.addSubview(todayCountLab) view.addSubview(todayUnitLab) todayCountLab.layoutChain .leftToView(title, offset: 4) .bottom(10) todayUnitLab.layoutChain .leftToRightOfView(todayCountLab, offset: 7) .bottomToView(todayCountLab, offset: -12) return view }() lazy var todayCountLab: UILabel = { let lab = UILabel() lab.text = "0" lab.font = .systemFont(ofSize: 50, weight: .heavy) lab.textColor = UIColor(hexStr: "#00ADFE") return lab }() lazy var todayUnitLab: UILabel = { let lab = UILabel() lab.text = "个" lab.font = .systemFont(ofSize: 16, weight: .bold) lab.textColor = UIColor(hexStr: "#293445") return lab }() lazy var appsSectionTitle: UIView = { makeSectionTitle("锁定应用") }() lazy var appsCard: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 26 return view }() lazy var appsHeaderRow: UIView = { let row = UIView() let left = UILabel() left.text = "选择锁定TA的app" left.font = .systemFont(ofSize: 14, weight: .medium) left.textColor = UIColor(hexStr: "#293445") row.addSubview(left) left.layoutChain.left().centerY() row.addSubview(notFetchedView) notFetchedView.layoutChain.right().centerY().height(28) row.layoutChain.height(28) return row }() lazy var notFetchedView: UIView = { let view = UIView() let warn = UIView() warn.backgroundColor = UIColor(hexStr: "#FA5151") warn.cornerRadius = 5 let warnText = UILabel() warnText.text = "!" warnText.textColor = .white warnText.font = .systemFont(ofSize: 8) view.addSubview(warn) warn.addSubview(warnText) warnText.layoutChain.centerX().centerY() let notFetchedText = UILabel() notFetchedText.text = "未获取" notFetchedText.textColor = UIColor(hexStr: "#293445") notFetchedText.font = .systemFont(ofSize: 12, weight: .medium) view.addSubview(notFetchedText) let arrow = UIImageView(image: UIImage(named: "LockDistract/arrow_right")) view.addSubview(arrow) view.addSubview(notFetchedText) arrow.layoutChain .right(14) .width(10) .heightToWidth(1) .centerY() notFetchedText.layoutChain .rightToLeftOfView(arrow) .centerY() warn.layoutChain .rightToLeftOfView(notFetchedText, offset: -4) .centerY() .width(10).height(10) return view }() lazy var unknownAppIcon: UIImageView = { let iv = UIImageView(image: UIImage(named: "LockDistract/app_unknown")) iv.contentMode = .scaleAspectFit return iv }() lazy var styleSectionTitle: UIView = { makeSectionTitle("锁定界面样式") }() lazy var styleCard: UIView = { let card = UIView() card.backgroundColor = .white card.cornerRadius = 26 let wallLab = UILabel() wallLab.text = "锁定壁纸图片" wallLab.font = .systemFont(ofSize: 13, weight: .medium) wallLab.textColor = UIColor(hexStr: "#8A94A6") card.addSubview(wallLab) wallLab.layoutChain.top(16).left(14) card.addSubview(wallpaperCV) wallpaperCV.layoutChain .topToBottomOfView(wallLab, offset: 10) .edgesHorzontal(14) .height(74) let copyLab = UILabel() copyLab.text = "锁定文案" copyLab.font = .systemFont(ofSize: 13, weight: .medium) copyLab.textColor = UIColor(hexStr: "#8A94A6") card.addSubview(copyLab) copyLab.layoutChain.topToBottomOfView(wallpaperCV, offset: 16).left(14) let input = UIView() input.backgroundColor = UIColor(hexStr: "#F5F6F8") input.cornerRadius = 12 card.addSubview(input) input.layoutChain .topToBottomOfView(copyLab, offset: 10) .edgesHorzontal(14) .height(44) .bottom(16) input.addSubview(copyTF) input.addSubview(copyCountLab) copyCountLab.layoutChain.centerY().right(12).width(35) copyTF.layoutChain.edgesVertical().left(12).rightToLeftOfView(copyCountLab, offset: -8) return card }() lazy var wallpaperCV: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 50, height: 74) layout.minimumLineSpacing = 12 layout.minimumInteritemSpacing = 0 let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.register(LockDistractWallpaperCell.self) cv.dataSource = self cv.delegate = self return cv }() lazy var copyTF: UITextField = { let tf = UITextField() tf.font = .systemFont(ofSize: 14, weight: .medium) tf.textColor = UIColor(hexStr: "#293445") tf.placeholderColor(placeholder: "随便说点什么吧~", color: UIColor(hexStr: "#999999"), font: .systemFont(ofSize: 14, weight: .medium)) return tf }() lazy var copyCountLab: UILabel = { let lab = UILabel() lab.text = "0/20" lab.font = .systemFont(ofSize: 14, weight: .medium) lab.textColor = UIColor(hexStr: "#AAAAAA") return lab }() lazy var tipRow: UIView = { let row = UIView() let icon = UIImageView(image: UIImage(systemName: "info.circle.fill")) icon.tintColor = UIColor(hexStr: "#16B3FF") let lab = UILabel() lab.text = "锁定功能只有在圈主的情况下才能使用哟~" lab.font = .systemFont(ofSize: 12, weight: .regular) lab.textColor = UIColor(hexStr: "#00ADFE") lab.numberOfLines = 0 row.addSubview(icon) row.addSubview(lab) icon.layoutChain.left().top(1).width(14).height(14) lab.layoutChain.leftToRightOfView(icon, offset: 6).right().top().bottom() return row }() lazy var lockBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("锁定", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = FontManager.boboBold(18) btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) btn.cornerRadius = 20 return btn }() private func makeSectionTitle(_ text: String) -> UIView { let row = UIView() let star = UIImageView(image: UIImage(named: "LockDistract/section_star")) // 切图目前带黑底,先用 SF Symbol 保证白底可读;资源补透明图后可改回 LockDistract/section_star // let config = UIImage.SymbolConfiguration(pointSize: 12, weight: .bold) // star.image = UIImage(systemName: "sparkle", withConfiguration: config) // star.tintColor = UIColor(hexStr: "#16B3FF") // star.contentMode = .scaleAspectFit let lab = UILabel() lab.text = text lab.font = .systemFont(ofSize: 16, weight: .bold) lab.textColor = UIColor(hexStr: "#1F2A44") row.addSubview(star) row.addSubview(lab) star.layoutChain.left().centerY().width(14).height(14) lab.layoutChain.leftToRightOfView(star, offset: 6).right().centerY() row.layoutChain.height(22) return row } } extension LockDistractView: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { wallpaperNames.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(for: indexPath) as LockDistractWallpaperCell cell.configure(imageName: wallpaperNames[indexPath.item], isSelected: indexPath.item == selectedWallpaperIndex) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectedWallpaperIndex = indexPath.item collectionView.reloadData() } } final class LockDistractWallpaperCell: UICollectionViewCell { private let frameView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F6F6F6") view.cornerRadius = 14 return view }() private let imageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.cornerRadius = 10 return iv }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(frameView) frameView.addSubview(imageView) frameView.layoutChain.edges() imageView.layoutChain .centerY() .centerX() .width(40) .height(40) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(imageName: String, isSelected: Bool) { imageView.image = UIImage(named: imageName) frameView.backgroundColor = UIColor(hexStr: isSelected ? "#EAF8FF" : "#F6F6F6") frameView.borderWidth = isSelected ? 1 : 0 frameView.borderColor = UIColor(hexStr: isSelected ? "#00ADFE" : "") } }