Bark/View/MessageList/MessageItemView.swift
2025-09-05 11:47:32 +08:00

284 lines
9.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 = .scaleAspectFit
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: MessageItemView) -> Void)?
static var imageCache: ImageCache?
static var imageCacheCreatedTime: Date?
///
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)
self.isAccessibilityElement = true
self.subviews.forEach { $0.isAccessibilityElement = false }
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 {
///
/// - Note: date Kingfisher/DiskStorage 使 maybeCached Set
/// Bark NotificationServiceExtension 使 ImageCache APP ImageCache maybeCached
/// - Parameter date:
/// - Returns:
func getImageCache(date: Date) -> ImageCache {
if let cache = MessageItemView.imageCache, let createdTime = MessageItemView.imageCacheCreatedTime, createdTime > date {
return cache
}
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl)
MessageItemView.imageCache = cache
MessageItemView.imageCacheCreatedTime = Date()
return cache ?? ImageCache.default
}
func setMessage(message: MessageItemModel) {
self.bodyLabel.attributedText = message.attributedText
self.accessibilityLabel = message.attributedText?.string
self.dateLabel.text = message.dateText
if let image = message.image {
imageView.isHidden = false
// 使
remakeImageViewConstraints(width: 200, height: 100)
//
imageView.removeImageViewer()
// loadDiskFileSynchronously
imageView.kf.setImage(with: URL(string: image), options: [.targetCache(getImageCache(date: message.createDate ?? Date())), .keepCurrentImageWhileLoading, .loadDiskFileSynchronously]) { [weak self] result in
guard let self else { return }
guard let image = try? result.get().image else {
self.imageView.image = nil
return
}
//
let isDarkMode = UIScreen.main.traitCollection.userInterfaceStyle == .dark
var options: [ImageViewerOption] = [
.closeIcon(UIImage(named: "back")!),
.theme(isDarkMode ? .dark : .light),
.contentMode(.scaleAspectFit)
]
if #available(iOS 14.0, *) {
options.append(.rightNavItemTitle("save".localized, onTap: { [weak self] _ in
// image
self?.saveImageToAlbum(image)
}))
}
self.imageView.setupImageViewer(options: options)
layoutImageView(image: image)
}
} else {
imageView.isHidden = true
remakeImageViewConstraints(width: 0, height: 0)
}
}
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
}
remakeImageViewConstraints(width: width, height: height)
}
func remakeImageViewConstraints(width: CGFloat, height: CGFloat) {
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: "noPermission".localized)
}
return
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image)
}) { success, error in
DispatchQueue.main.async {
if success {
SVProgressHUD.showSuccess(withStatus: "saveSuccess".localized)
} else {
SVProgressHUD.showError(withStatus: error?.localizedDescription)
}
}
}
}
}
}
extension UIImageView {
func removeImageViewer() {
var _tapRecognizer: UIGestureRecognizer?
gestureRecognizers?.forEach {
// TapWithDataRecognizer
if "\(type(of: $0))" == "TapWithDataRecognizer" {
_tapRecognizer = $0
}
}
if let _tapRecognizer {
self.removeGestureRecognizer(_tapRecognizer)
}
}
}