Merge branch 'fix/profile-timeline' into develop

This commit is contained in:
CMK 2021-06-17 13:45:24 +08:00
commit 7b52da8757
4 changed files with 71 additions and 61 deletions

View File

@ -251,7 +251,7 @@ extension StatusSection {
default: return 0.7 default: return 0.7
} }
}() }()
return CGSize(width: maxWidth, height: maxWidth * scale) return CGSize(width: maxWidth, height: floor(maxWidth * scale))
}() }()
let mosaics: [MosaicImageViewContainer.ConfigurableMosaic] = { let mosaics: [MosaicImageViewContainer.ConfigurableMosaic] = {
if mosaicImageViewModel.metas.count == 1 { if mosaicImageViewModel.metas.count == 1 {
@ -268,13 +268,16 @@ extension StatusSection {
let blurhashOverlayImageView = mosaic.blurhashOverlayImageView let blurhashOverlayImageView = mosaic.blurhashOverlayImageView
let meta = mosaicImageViewModel.metas[i] let meta = mosaicImageViewModel.metas[i]
// set blurhash image
meta.blurhashImagePublisher() meta.blurhashImagePublisher()
.receive(on: RunLoop.main)
.sink { image in .sink { image in
blurhashOverlayImageView.image = image blurhashOverlayImageView.image = image
} }
.store(in: &cell.disposeBag) .store(in: &cell.disposeBag)
let isSingleMosaicLayout = mosaics.count == 1
// set image
let imageSize = CGSize( let imageSize = CGSize(
width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale, width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale,
height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale
@ -282,12 +285,18 @@ extension StatusSection {
let request = ImageRequest( let request = ImageRequest(
url: meta.url, url: meta.url,
processors: [ processors: [
ImageProcessors.Resize(size: imageSize, contentMode: .aspectFill) ImageProcessors.Resize(
size: imageSize,
unit: .pixels,
contentMode: isSingleMosaicLayout ? .aspectFill : .aspectFit,
crop: isSingleMosaicLayout
)
] ]
) )
let options = ImageLoadingOptions( let options = ImageLoadingOptions(
transition: .fadeIn(duration: 0.2) transition: .fadeIn(duration: 0.2)
) )
Nuke.loadImage( Nuke.loadImage(
with: request, with: request,
options: options, options: options,
@ -296,28 +305,17 @@ extension StatusSection {
switch result { switch result {
case .failure: case .failure:
break break
case .success: case .success(let response)
statusItemAttribute.isImageLoaded.value = true statusItemAttribute.isImageLoaded.value = true
} }
} }
//imageView.af.setImage(
// withURL: meta.url,
// placeholderImage: UIImage.placeholder(color: .systemFill),
// imageTransition: .crossDissolve(0.2)
//) { response in
// switch response.result {
// case .success:
// statusItemAttribute.isImageLoaded.value = true
// case .failure:
// break
// }
//}
imageView.accessibilityLabel = meta.altText imageView.accessibilityLabel = meta.altText
Publishers.CombineLatest( Publishers.CombineLatest(
statusItemAttribute.isImageLoaded, statusItemAttribute.isImageLoaded,
statusItemAttribute.isRevealing statusItemAttribute.isRevealing
) )
.receive(on: RunLoop.main) .receive(on: DispatchQueue.main) // needs call immediately
.sink { [weak cell] isImageLoaded, isMediaRevealing in .sink { [weak cell] isImageLoaded, isMediaRevealing in
guard let cell = cell else { return } guard let cell = cell else { return }
guard isImageLoaded else { guard isImageLoaded else {

View File

@ -104,7 +104,7 @@ extension MosaicImageViewContainer {
imageViews = [] imageViews = []
blurhashOverlayImageViews = [] blurhashOverlayImageViews = []
container.spacing = 1 container.spacing = UIView.separatorLineHeight(of: self) * 2 // 2px
} }
struct ConfigurableMosaic { struct ConfigurableMosaic {
@ -120,12 +120,16 @@ extension MosaicImageViewContainer {
contentView.translatesAutoresizingMaskIntoConstraints = false contentView.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(contentView) container.addArrangedSubview(contentView)
let rect = AVMakeRect( let imageViewSize: CGSize = {
aspectRatio: aspectRatio, let rect = AVMakeRect(
insideRect: CGRect(origin: .zero, size: maxSize) aspectRatio: aspectRatio,
) insideRect: CGRect(origin: .zero, size: maxSize)
).integral
return rect.size
}()
let imageViewFrame = CGRect(origin: .zero, size: imageViewSize)
let imageView = UIImageView() let imageView = UIImageView(frame: imageViewFrame)
imageViews.append(imageView) imageViews.append(imageView)
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = ContentWarningOverlayView.cornerRadius imageView.layer.cornerRadius = ContentWarningOverlayView.cornerRadius
@ -138,9 +142,9 @@ extension MosaicImageViewContainer {
imageView.topAnchor.constraint(equalTo: contentView.topAnchor), imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
imageView.widthAnchor.constraint(equalToConstant: floor(rect.width)).priority(.required - 1), imageView.widthAnchor.constraint(equalToConstant: imageViewFrame.width).priority(.required - 1),
]) ])
containerHeightLayoutConstraint.constant = floor(rect.height) containerHeightLayoutConstraint.constant = imageViewFrame.height
containerHeightLayoutConstraint.isActive = true containerHeightLayoutConstraint.isActive = true
let blurhashOverlayImageView = UIImageView() let blurhashOverlayImageView = UIImageView()
@ -170,7 +174,7 @@ extension MosaicImageViewContainer {
return ConfigurableMosaic( return ConfigurableMosaic(
imageView: imageView, imageView: imageView,
blurhashOverlayImageView: blurhashOverlayImageView, blurhashOverlayImageView: blurhashOverlayImageView,
imageViewSize: maxSize imageViewSize: imageViewSize
) )
} }
@ -181,6 +185,7 @@ extension MosaicImageViewContainer {
} }
let maxHeight = maxSize.height let maxHeight = maxSize.height
let spacing: CGFloat = 1
containerHeightLayoutConstraint.constant = maxHeight containerHeightLayoutConstraint.constant = maxHeight
containerHeightLayoutConstraint.isActive = true containerHeightLayoutConstraint.isActive = true
@ -190,7 +195,7 @@ extension MosaicImageViewContainer {
[contentLeftStackView, contentRightStackView].forEach { stackView in [contentLeftStackView, contentRightStackView].forEach { stackView in
stackView.axis = .vertical stackView.axis = .vertical
stackView.distribution = .fillEqually stackView.distribution = .fillEqually
stackView.spacing = 1 stackView.spacing = spacing
} }
container.addArrangedSubview(contentLeftStackView) container.addArrangedSubview(contentLeftStackView)
container.addArrangedSubview(contentRightStackView) container.addArrangedSubview(contentRightStackView)
@ -310,22 +315,23 @@ extension MosaicImageViewContainer {
let imageViewSize: CGSize = { let imageViewSize: CGSize = {
switch (i, count) { switch (i, count) {
case (_, 4): case (_, 4):
return CGSize(width: maxSize.width * 0.5, height: maxSize.height * 0.5) return CGSize(width: maxSize.width * 0.5 - spacing, height: maxSize.height * 0.5 - spacing)
case (i, 3): case (i, 3):
let width = maxSize.width * 0.5 let width = maxSize.width * 0.5 - spacing
if i == 0 { if i == 0 {
return CGSize(width: width, height: maxSize.height) return CGSize(width: width, height: maxSize.height)
} else { } else {
return CGSize(width: width, height: maxSize.height * 0.5) return CGSize(width: width, height: maxSize.height * 0.5 - spacing)
} }
case (_, 2): case (_, 2):
let width = maxSize.width * 0.5 let width = maxSize.width * 0.5 - spacing
return CGSize(width: width, height: maxSize.height) return CGSize(width: width, height: maxSize.height)
default: default:
assertionFailure() assertionFailure()
return maxSize return maxSize
} }
}() }()
imageView.frame.size = imageViewSize
let mosaic = ConfigurableMosaic( let mosaic = ConfigurableMosaic(
imageView: imageView, imageView: imageView,
blurhashOverlayImageView: blurhashOverlayImageView, blurhashOverlayImageView: blurhashOverlayImageView,

View File

@ -15,22 +15,25 @@ final class BlurhashImageCacheService {
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent) let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent)
func image(blurhash: String, size: CGSize, url: URL) -> AnyPublisher<UIImage?, Never> { func image(blurhash: String, size: CGSize, url: URL) -> AnyPublisher<UIImage?, Never> {
let key = Key(blurhash: blurhash, size: size, url: url)
if let image = self.cache.object(forKey: key) {
return Just(image).eraseToAnyPublisher()
}
return Future { promise in return Future { promise in
self.workingQueue.async { self.workingQueue.async {
let key = Key(blurhash: blurhash, size: size, url: url) guard let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size, url: url) else {
guard let image = self.cache.object(forKey: key) else { promise(.success(nil))
if let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size, url: url) {
self.cache.setObject(image, forKey: key)
promise(.success(image))
} else {
promise(.success(nil))
}
return return
} }
self.cache.setObject(image, forKey: key)
promise(.success(image)) promise(.success(image))
} }
} }
.receive(on: RunLoop.main)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
static func blurhashImage(blurhash: String, size: CGSize, url: URL) -> UIImage? { static func blurhashImage(blurhash: String, size: CGSize, url: URL) -> UIImage? {
@ -55,13 +58,7 @@ final class BlurhashImageCacheService {
} }
extension BlurhashImageCacheService { extension BlurhashImageCacheService {
class Key: Hashable { class Key: NSObject {
static func == (lhs: BlurhashImageCacheService.Key, rhs: BlurhashImageCacheService.Key) -> Bool {
return lhs.blurhash == rhs.blurhash
&& lhs.size == rhs.size
&& lhs.url == rhs.url
}
let blurhash: String let blurhash: String
let size: CGSize let size: CGSize
let url: URL let url: URL
@ -72,11 +69,19 @@ extension BlurhashImageCacheService {
self.url = url self.url = url
} }
func hash(into hasher: inout Hasher) { override func isEqual(_ object: Any?) -> Bool {
hasher.combine(blurhash) guard let object = object as? Key else { return false }
hasher.combine(size.width) return object.blurhash == blurhash
hasher.combine(size.height) && object.size == size
hasher.combine(url) && object.url == url
} }
override var hash: Int {
return blurhash.hashValue ^
size.width.hashValue ^
size.height.hashValue ^
url.hashValue
}
} }
} }

View File

@ -33,7 +33,7 @@ final class PlaceholderImageCacheService {
} }
extension PlaceholderImageCacheService { extension PlaceholderImageCacheService {
class Key: Hashable { class Key: NSObject {
let color: UIColor let color: UIColor
let size: CGSize let size: CGSize
let cornerRadius: CGFloat let cornerRadius: CGFloat
@ -44,17 +44,18 @@ extension PlaceholderImageCacheService {
self.cornerRadius = cornerRadius self.cornerRadius = cornerRadius
} }
static func == (lhs: PlaceholderImageCacheService.Key, rhs: PlaceholderImageCacheService.Key) -> Bool { override func isEqual(_ object: Any?) -> Bool {
return lhs.color == rhs.color guard let object = object as? Key else { return false }
&& lhs.size == rhs.size return object.color == color
&& lhs.cornerRadius == rhs.cornerRadius && object.size == size
&& object.cornerRadius == cornerRadius
} }
func hash(into hasher: inout Hasher) { override var hash: Int {
hasher.combine(color) return color.hashValue ^
hasher.combine(size.width) size.width.hashValue ^
hasher.combine(size.height) size.height.hashValue ^
hasher.combine(cornerRadius) cornerRadius.hashValue
} }
} }
} }