|
|
@@ -18,8 +18,12 @@ class LNProfileUserInfoView: UIView {
|
|
|
private let followCountLabel = UILabel()
|
|
|
|
|
|
private let scoreLabel = UILabel()
|
|
|
+ private let likeIc = UIImageView()
|
|
|
+ private let likeContainer = UIView()
|
|
|
|
|
|
private var curDetail: LNUserProfileVO?
|
|
|
+ private var likeInfo: LNUserLikeInfoResponse?
|
|
|
+ private var isOperating = false
|
|
|
|
|
|
override init(frame: CGRect) {
|
|
|
super.init(frame: frame)
|
|
|
@@ -31,12 +35,29 @@ class LNProfileUserInfoView: UIView {
|
|
|
userNameLabel.text = detail.nickname
|
|
|
genderView.update(detail.gender, detail.age)
|
|
|
idLabel.text = "ID \(detail.userNo)"
|
|
|
- followCountLabel.text = .init(key: "A00234", detail.fansCount)
|
|
|
|
|
|
- scoreLabel.text = "\(detail.star)"
|
|
|
- scoreLabel.superview?.isHidden = !detail.playmate
|
|
|
+ followCountLabel.text = .init(key: "A00234", detail.fansCount.formattedAsShortNumber())
|
|
|
|
|
|
curDetail = detail
|
|
|
+
|
|
|
+ loadLikeInfo()
|
|
|
+ }
|
|
|
+
|
|
|
+ func toLikeUser(onlyLike: Bool = false) {
|
|
|
+ guard let curDetail, let likeInfo else { return }
|
|
|
+ if onlyLike, likeInfo.liked { return }
|
|
|
+ guard !isOperating else { return }
|
|
|
+ let willLike = !likeInfo.liked
|
|
|
+ isOperating = true
|
|
|
+ LNProfileManager.shared.likeUser(uid: curDetail.userNo, like: willLike)
|
|
|
+ { [weak self] success in
|
|
|
+ guard let self else { return }
|
|
|
+ isOperating = false
|
|
|
+ guard success else { return }
|
|
|
+ likeInfo.count += (likeInfo.liked ? -1 : 1)
|
|
|
+ likeInfo.liked.toggle()
|
|
|
+ updateLikeView(animateLike: willLike)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
@@ -44,12 +65,124 @@ class LNProfileUserInfoView: UIView {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+extension LNProfileUserInfoView {
|
|
|
+ private func loadLikeInfo() {
|
|
|
+ guard let uid = curDetail?.userNo else { return }
|
|
|
+ LNProfileManager.shared.getUserLikeInfo(uid: uid) { [weak self] info in
|
|
|
+ guard let self else { return }
|
|
|
+ guard let info else { return }
|
|
|
+ likeInfo = info
|
|
|
+ updateLikeView()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateLikeView(animateLike: Bool = false) {
|
|
|
+ guard let likeInfo else { return }
|
|
|
+ likeIc.superview?.isHidden = false
|
|
|
+ scoreLabel.text = likeInfo.count.formattedAsShortNumber()
|
|
|
+ scoreLabel.isHidden = likeInfo.count == 0
|
|
|
+ if likeInfo.liked {
|
|
|
+ likeIc.image = .icLikeFilled
|
|
|
+ } else {
|
|
|
+ likeIc.image = .icLikeEmpty.withTintColor(.text_1, renderingMode: .alwaysOriginal)
|
|
|
+ }
|
|
|
+
|
|
|
+ if animateLike {
|
|
|
+ playLikeAnimation()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func playLikeAnimation() {
|
|
|
+ likeIc.layer.removeAllAnimations()
|
|
|
+ likeContainer.layer.removeAnimation(forKey: "likeRing")
|
|
|
+ likeContainer.layer.removeAnimation(forKey: "likeBurst")
|
|
|
+
|
|
|
+ likeIc.transform = CGAffineTransform(scaleX: 0.72, y: 0.72)
|
|
|
+ UIView.animate(withDuration: 0.18, delay: 0, usingSpringWithDamping: 0.52, initialSpringVelocity: 0.6) {
|
|
|
+ self.likeIc.transform = CGAffineTransform(scaleX: 1.26, y: 1.26)
|
|
|
+ } completion: { _ in
|
|
|
+ UIView.animate(withDuration: 0.16) {
|
|
|
+ self.likeIc.transform = .identity
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let center = CGPoint(x: likeIc.center.x, y: likeIc.center.y)
|
|
|
+ let ringLayer = CAShapeLayer()
|
|
|
+ ringLayer.path = UIBezierPath(ovalIn: CGRect(x: center.x - 10, y: center.y - 10, width: 20, height: 20)).cgPath
|
|
|
+ ringLayer.fillColor = UIColor.clear.cgColor
|
|
|
+ ringLayer.strokeColor = UIColor.fill_6.withAlphaComponent(0.35).cgColor
|
|
|
+ ringLayer.lineWidth = 2
|
|
|
+ likeContainer.layer.addSublayer(ringLayer)
|
|
|
+
|
|
|
+ let ringScale = CABasicAnimation(keyPath: "transform.scale")
|
|
|
+ ringScale.fromValue = 0.3
|
|
|
+ ringScale.toValue = 1.9
|
|
|
+
|
|
|
+ let ringOpacity = CABasicAnimation(keyPath: "opacity")
|
|
|
+ ringOpacity.fromValue = 0.9
|
|
|
+ ringOpacity.toValue = 0
|
|
|
+
|
|
|
+ let ringGroup = CAAnimationGroup()
|
|
|
+ ringGroup.animations = [ringScale, ringOpacity]
|
|
|
+ ringGroup.duration = 0.42
|
|
|
+ ringGroup.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
|
+ ringGroup.isRemovedOnCompletion = false
|
|
|
+ ringGroup.fillMode = .forwards
|
|
|
+
|
|
|
+ CATransaction.begin()
|
|
|
+ CATransaction.setCompletionBlock {
|
|
|
+ ringLayer.removeFromSuperlayer()
|
|
|
+ }
|
|
|
+ ringLayer.add(ringGroup, forKey: "likeRing")
|
|
|
+ CATransaction.commit()
|
|
|
+
|
|
|
+ let burstLayer = CAReplicatorLayer()
|
|
|
+ burstLayer.frame = likeContainer.bounds
|
|
|
+ burstLayer.instanceCount = 6
|
|
|
+ burstLayer.instanceTransform = CATransform3DMakeRotation(.pi * 2 / 6, 0, 0, 1)
|
|
|
+ burstLayer.instanceDelay = 0.015
|
|
|
+ likeContainer.layer.addSublayer(burstLayer)
|
|
|
+
|
|
|
+ let dotLayer = CALayer()
|
|
|
+ dotLayer.backgroundColor = UIColor.fill_6.cgColor
|
|
|
+ dotLayer.frame = CGRect(x: center.x - 1.5, y: center.y - 17, width: 3, height: 7)
|
|
|
+ dotLayer.cornerRadius = 1.5
|
|
|
+ burstLayer.addSublayer(dotLayer)
|
|
|
+
|
|
|
+ let burstMove = CABasicAnimation(keyPath: "transform.translation.y")
|
|
|
+ burstMove.fromValue = 0
|
|
|
+ burstMove.toValue = -9
|
|
|
+
|
|
|
+ let burstScale = CABasicAnimation(keyPath: "transform.scale")
|
|
|
+ burstScale.fromValue = 0.6
|
|
|
+ burstScale.toValue = 1
|
|
|
+
|
|
|
+ let burstOpacity = CABasicAnimation(keyPath: "opacity")
|
|
|
+ burstOpacity.fromValue = 1
|
|
|
+ burstOpacity.toValue = 0
|
|
|
+
|
|
|
+ let burstGroup = CAAnimationGroup()
|
|
|
+ burstGroup.animations = [burstMove, burstScale, burstOpacity]
|
|
|
+ burstGroup.duration = 0.32
|
|
|
+ burstGroup.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
|
+ burstGroup.isRemovedOnCompletion = false
|
|
|
+ burstGroup.fillMode = .forwards
|
|
|
+
|
|
|
+ CATransaction.begin()
|
|
|
+ CATransaction.setCompletionBlock {
|
|
|
+ burstLayer.removeFromSuperlayer()
|
|
|
+ }
|
|
|
+ dotLayer.add(burstGroup, forKey: "likeBurst")
|
|
|
+ CATransaction.commit()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
extension LNProfileUserInfoView {
|
|
|
private func setupViews() {
|
|
|
let star = buildStarView()
|
|
|
addSubview(star)
|
|
|
star.snp.makeConstraints { make in
|
|
|
- make.trailing.equalToSuperview().offset(-16)
|
|
|
+ make.trailing.equalToSuperview().offset(-18)
|
|
|
make.bottom.equalToSuperview().offset(-10)
|
|
|
}
|
|
|
|
|
|
@@ -117,31 +250,37 @@ extension LNProfileUserInfoView {
|
|
|
followCountLabel.textColor = .text_2
|
|
|
fansView.addSubview(followCountLabel)
|
|
|
followCountLabel.snp.makeConstraints { make in
|
|
|
- make.edges.equalToSuperview()
|
|
|
+ make.leading.equalToSuperview()
|
|
|
+ make.verticalEdges.equalToSuperview()
|
|
|
+ make.trailing.lessThanOrEqualToSuperview()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func buildStarView() -> UIView {
|
|
|
- let container = UIView()
|
|
|
+ likeContainer.isHidden = true
|
|
|
+ likeContainer.onTap { [weak self] in
|
|
|
+ guard let self else { return }
|
|
|
+ toLikeUser()
|
|
|
+ }
|
|
|
|
|
|
- let star = LNStarScoreView()
|
|
|
- star.icSize = 18
|
|
|
- star.score = 1.0
|
|
|
- container.addSubview(star)
|
|
|
- star.snp.makeConstraints { make in
|
|
|
- make.leading.equalToSuperview()
|
|
|
- make.verticalEdges.equalToSuperview()
|
|
|
+ likeIc.image = .icLikeEmpty.withTintColor(.text_1, renderingMode: .alwaysOriginal)
|
|
|
+ likeContainer.addSubview(likeIc)
|
|
|
+ likeIc.snp.makeConstraints { make in
|
|
|
+ make.horizontalEdges.equalToSuperview()
|
|
|
+ make.top.equalToSuperview()
|
|
|
+ make.width.height.equalTo(36)
|
|
|
}
|
|
|
|
|
|
- scoreLabel.font = .heading_h2
|
|
|
- scoreLabel.textColor = .text_1
|
|
|
- container.addSubview(scoreLabel)
|
|
|
+ scoreLabel.text = "0"
|
|
|
+ scoreLabel.font = .body_s
|
|
|
+ scoreLabel.textColor = .text_2
|
|
|
+ likeContainer.addSubview(scoreLabel)
|
|
|
scoreLabel.snp.makeConstraints { make in
|
|
|
- make.centerY.equalToSuperview()
|
|
|
- make.leading.equalTo(star.snp.trailing).offset(4)
|
|
|
- make.trailing.equalToSuperview()
|
|
|
+ make.centerX.equalToSuperview()
|
|
|
+ make.top.equalTo(likeIc.snp.bottom).offset(2)
|
|
|
+ make.bottom.equalToSuperview()
|
|
|
}
|
|
|
|
|
|
- return container
|
|
|
+ return likeContainer
|
|
|
}
|
|
|
}
|