Bark/View/MessageList/MessageItemView.swift
2025-05-23 10:40:52 +08:00

241 lines
7.9 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.

//
// MessageItemView.swift
// Bark
//
// Created by huangfeng on 12/23/24.
// Copyright © 2024 Fin. All rights reserved.
//
import ImageViewer_swift
import Kingfisher
import Photos
import SVProgressHUD
import UIKit
class MessageItemView: UIView {
let panel: UIView = {
let view = UIView()
view.layer.cornerRadius = 10
view.backgroundColor = BKColor.background.secondary
view.clipsToBounds = true
return view
}()
let blackMaskView: UIView = {
let view = UIView()
view.layer.cornerRadius = 10
view.backgroundColor = BKColor.black
view.isUserInteractionEnabled = false
view.alpha = 0
return view
}()
let bodyLabel: CustomTapTextView = {
let label = CustomTapTextView()
label.font = UIFont.preferredFont(ofSize: 14)
label.adjustsFontForContentSizeCategory = true
label.textColor = BKColor.grey.darken4
return label
}()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 4
imageView.clipsToBounds = true
return imageView
}()
let contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.alignment = .leading
return stackView
}()
let dateLabel: UILabel = {
let label = BKLabel()
label.hitTestSlop = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5)
label.font = UIFont.preferredFont(ofSize: 11, weight: .medium)
label.adjustsFontForContentSizeCategory = true
label.textColor = BKColor.grey.base
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer())
return label
}()
var message: MessageItemModel? = nil {
didSet {
guard let message else {
return
}
setMessage(message: message)
}
}
var maskAlpha: CGFloat = 0 {
didSet {
blackMaskView.alpha = maskAlpha
}
}
var isShowSubviews: Bool = true {
didSet {
for view in [bodyLabel, dateLabel] {
view.alpha = isShowSubviews ? 1 : 0
}
}
}
var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)?
///
lazy var imageCache: ImageCache = {
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl)
return cache ?? KingfisherManager.shared.cache
}()
init() {
super.init(frame: .zero)
self.backgroundColor = BKColor.background.primary
self.addSubview(panel)
panel.addSubview(contentStackView)
panel.addSubview(dateLabel)
panel.addSubview(blackMaskView)
contentStackView.addArrangedSubview(bodyLabel)
contentStackView.addArrangedSubview(imageView)
layoutView()
//
dateLabel.gestureRecognizers?.first?.rx.event.subscribe(onNext: { [weak self] _ in
guard let self else { return }
if self.message?.dateStyle != .exact {
self.message?.dateStyle = .exact
} else {
self.message?.dateStyle = .relative
}
self.dateLabel.text = self.message?.dateText
}).disposed(by: rx.disposeBag)
self.bodyLabel.customTapAction = { [weak self] in
guard let self, let message = self.message else { return }
self.tapAction?(message, self)
}
panel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layoutView() {
contentStackView.snp.makeConstraints { make in
make.top.equalTo(16)
make.left.equalTo(12)
make.right.equalTo(-12)
}
dateLabel.snp.makeConstraints { make in
make.left.equalTo(contentStackView)
make.top.equalTo(contentStackView.snp.bottom).offset(12)
make.bottom.equalTo(panel).offset(-12).priority(.medium)
}
panel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
blackMaskView.snp.makeConstraints { make in
make.edges.equalTo(panel)
}
}
@objc func tap() {
guard let message else { return }
self.tapAction?(message, self)
}
}
extension MessageItemView {
func setMessage(message: MessageItemModel) {
self.bodyLabel.attributedText = message.attributedText
self.dateLabel.text = message.dateText
if let image = message.image {
imageView.isHidden = false
// loadDiskFileSynchronously
imageView.kf.setImage(with: URL(string: image), options: [.targetCache(imageCache), .keepCurrentImageWhileLoading, .loadDiskFileSynchronously]) { [weak self] result in
guard let self else { return }
guard let image = try? result.get().image else {
return
}
//
let isDarkMode = UIScreen.main.traitCollection.userInterfaceStyle == .dark
var options: [ImageViewerOption] = [
.closeIcon(UIImage(named: "back")!),
.theme(isDarkMode ? .dark : .light)
]
if #available(iOS 14.0, *) {
options.append(.rightNavItemTitle(NSLocalizedString("save"), onTap: { _ in
// image
self.saveImageToAlbum(image)
}))
}
self.imageView.setupImageViewer(options: options)
layoutImageView(image: image)
}
} else {
imageView.isHidden = true
}
}
func layoutImageView(image: UIImage) {
let scale = image.size.height / image.size.width
// iPad 500
var width = min(min(500, UIScreen.main.bounds.width - 32 - 24), image.width)
var height = width * scale
if height > 400 {
width = 400 / scale
height = 400
}
imageView.snp.remakeConstraints { make in
make.width.equalTo(width)
make.height.equalTo(height)
}
}
}
@available(iOS 14.0, *)
extension MessageItemView {
func saveImageToAlbum(_ image: UIImage) {
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
guard status == .authorized || status == .limited else {
DispatchQueue.main.async {
SVProgressHUD.showInfo(withStatus: NSLocalizedString("noPermission"))
}
return
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image)
}) { success, error in
DispatchQueue.main.async {
if success {
SVProgressHUD.showSuccess(withStatus: NSLocalizedString("saveSuccess"))
} else {
SVProgressHUD.showError(withStatus: error?.localizedDescription)
}
}
}
}
}
}