forked from zelo72/mastodon-ios
Merge branch 'fix/profile-timeline' into develop
This commit is contained in:
commit
7b52da8757
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue