Added a 20MB content length limit for all image files
Changelog-Changed: Added a 20MB content length limit for all image files Closes: #335
This commit is contained in:
@@ -206,9 +206,9 @@
|
|||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
||||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||||
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C45AE70297353390031D7BC /* KFImageModel.swift */; };
|
|
||||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
||||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
||||||
|
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; };
|
||||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
@@ -515,9 +515,9 @@
|
|||||||
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
|
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
|
||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||||
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||||
7C45AE70297353390031D7BC /* KFImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFImageModel.swift; sourceTree = "<group>"; };
|
|
||||||
7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; };
|
7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; };
|
||||||
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
|
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
|
||||||
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; };
|
||||||
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
||||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
@@ -663,7 +663,6 @@
|
|||||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
|
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
|
||||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
|
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
|
||||||
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
|
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
|
||||||
7C45AE70297353390031D7BC /* KFImageModel.swift */,
|
|
||||||
4CF0ABD32980996B00D66079 /* Report.swift */,
|
4CF0ABD32980996B00D66079 /* Report.swift */,
|
||||||
4CF0ABDD2981A69500D66079 /* MutelistModel.swift */,
|
4CF0ABDD2981A69500D66079 /* MutelistModel.swift */,
|
||||||
3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */,
|
3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */,
|
||||||
@@ -789,6 +788,7 @@
|
|||||||
4CB883B5297730E400DC99E7 /* LNUrls.swift */,
|
4CB883B5297730E400DC99E7 /* LNUrls.swift */,
|
||||||
3AB72AB8298ECF30004BB58C /* Translator.swift */,
|
3AB72AB8298ECF30004BB58C /* Translator.swift */,
|
||||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
|
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
|
||||||
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1257,7 +1257,6 @@
|
|||||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||||
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */,
|
|
||||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
||||||
@@ -1265,6 +1264,7 @@
|
|||||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
||||||
|
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
||||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
||||||
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
|
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
|
||||||
|
|||||||
@@ -66,20 +66,11 @@ struct ImageContextMenuModifier: ViewModifier {
|
|||||||
|
|
||||||
private struct ImageContainerView: View {
|
private struct ImageContainerView: View {
|
||||||
|
|
||||||
@ObservedObject var imageModel: KFImageModel
|
let url: URL?
|
||||||
|
|
||||||
@State private var image: UIImage?
|
@State private var image: UIImage?
|
||||||
@State private var showShareSheet = false
|
@State private var showShareSheet = false
|
||||||
|
|
||||||
init(url: URL?) {
|
|
||||||
self.imageModel = KFImageModel(
|
|
||||||
url: url,
|
|
||||||
fallbackUrl: nil,
|
|
||||||
maxByteSize: 2000000, // 2 MB
|
|
||||||
downsampleSize: CGSize(width: 400, height: 400)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ImageHandler: ImageModifier {
|
private struct ImageHandler: ImageModifier {
|
||||||
@Binding var handler: UIImage?
|
@Binding var handler: UIImage?
|
||||||
|
|
||||||
@@ -91,30 +82,17 @@ private struct ImageContainerView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
KFAnimatedImage(imageModel.url)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
.imageContext(.note)
|
||||||
.processingQueue(.dispatch(.global(qos: .background)))
|
|
||||||
.cacheOriginalImage()
|
|
||||||
.configure { view in
|
.configure { view in
|
||||||
view.framePreloadCount = 1
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.scaleFactor(UIScreen.main.scale)
|
|
||||||
.loadDiskFileSynchronously()
|
|
||||||
.fade(duration: 0.1)
|
|
||||||
.imageModifier(ImageHandler(handler: $image))
|
.imageModifier(ImageHandler(handler: $image))
|
||||||
.onFailure { _ in
|
|
||||||
imageModel.downloadFailed()
|
|
||||||
}
|
|
||||||
.id(imageModel.refreshID)
|
|
||||||
.clipped()
|
.clipped()
|
||||||
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
|
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
|
||||||
.sheet(isPresented: $showShareSheet) {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
ShareSheet(activityItems: [imageModel.url])
|
ShareSheet(activityItems: [url])
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update ImageCarousel with serializer and processor
|
|
||||||
// .serialize(by: imageModel.serializer)
|
|
||||||
// .setProcessor(imageModel.processor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,12 +199,8 @@ struct ImageCarousel: View {
|
|||||||
.foregroundColor(Color.clear)
|
.foregroundColor(Color.clear)
|
||||||
.overlay {
|
.overlay {
|
||||||
KFAnimatedImage(url)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
.imageContext(.note)
|
||||||
.processingQueue(.dispatch(.global(qos: .background)))
|
|
||||||
.cancelOnDisappear(true)
|
.cancelOnDisappear(true)
|
||||||
.backgroundDecode()
|
|
||||||
.cacheOriginalImage()
|
|
||||||
.scaleFactor(UIScreen.main.scale)
|
|
||||||
.configure { view in
|
.configure { view in
|
||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
//
|
|
||||||
// KFImageModel.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Oleg Abalonski on 1/11/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Kingfisher
|
|
||||||
|
|
||||||
class KFImageModel: ObservableObject {
|
|
||||||
|
|
||||||
let url: URL?
|
|
||||||
let fallbackUrl: URL?
|
|
||||||
let processor: ImageProcessor
|
|
||||||
let serializer: CacheSerializer
|
|
||||||
|
|
||||||
@Published var refreshID = ""
|
|
||||||
|
|
||||||
init(url: URL?, fallbackUrl: URL?, maxByteSize: Int, downsampleSize: CGSize) {
|
|
||||||
self.url = url
|
|
||||||
self.fallbackUrl = fallbackUrl
|
|
||||||
self.processor = CustomImageProcessor(maxSize: maxByteSize, downsampleSize: downsampleSize)
|
|
||||||
self.serializer = CustomCacheSerializer(maxSize: maxByteSize, downsampleSize: downsampleSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func refresh() -> Void {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.refreshID = UUID().uuidString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cache(_ image: UIImage, forKey key: String) -> Void {
|
|
||||||
KingfisherManager.shared.cache.store(image, forKey: key, processorIdentifier: processor.identifier) { _ in
|
|
||||||
self.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadFailed() -> Void {
|
|
||||||
guard let url = url, let fallbackUrl = fallbackUrl else { return }
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
KingfisherManager.shared.downloader.downloadImage(with: fallbackUrl) { result in
|
|
||||||
|
|
||||||
var fallbackImage: UIImage {
|
|
||||||
switch result {
|
|
||||||
case .success(let imageLoadingResult):
|
|
||||||
return imageLoadingResult.image
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
return UIImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cache(fallbackImage, forKey: url.absoluteString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CustomImageProcessor: ImageProcessor {
|
|
||||||
|
|
||||||
let maxSize: Int
|
|
||||||
let downsampleSize: CGSize
|
|
||||||
|
|
||||||
let identifier = "com.damus.customimageprocessor"
|
|
||||||
|
|
||||||
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
|
||||||
|
|
||||||
switch item {
|
|
||||||
case .image(_):
|
|
||||||
// This case will never run
|
|
||||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
|
||||||
case .data(let data):
|
|
||||||
|
|
||||||
// Handle large image size
|
|
||||||
if data.count > maxSize {
|
|
||||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle SVG image
|
|
||||||
if let dataString = String(data: data, encoding: .utf8),
|
|
||||||
let svg = SVG(dataString) {
|
|
||||||
|
|
||||||
let render = UIGraphicsImageRenderer(size: svg.size)
|
|
||||||
let image = render.image { context in
|
|
||||||
svg.draw(in: context.cgContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return image.kf.scaled(to: options.scaleFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CustomCacheSerializer: CacheSerializer {
|
|
||||||
|
|
||||||
let maxSize: Int
|
|
||||||
let downsampleSize: CGSize
|
|
||||||
|
|
||||||
func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? {
|
|
||||||
return DefaultCacheSerializer.default.data(with: image, original: original)
|
|
||||||
}
|
|
||||||
|
|
||||||
func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? {
|
|
||||||
if data.count > maxSize {
|
|
||||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultCacheSerializer.default.image(with: data, options: options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
148
damus/Util/KFOptionSetter+.swift
Normal file
148
damus/Util/KFOptionSetter+.swift
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
//
|
||||||
|
// KFOptionSetter+.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Oleg Abalonski on 2/15/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
extension KFOptionSetter {
|
||||||
|
|
||||||
|
func imageContext(_ imageContext: ImageContext) -> Self {
|
||||||
|
options.callbackQueue = .dispatch(.global(qos: .background))
|
||||||
|
options.processingQueue = .dispatch(.global(qos: .background))
|
||||||
|
options.downloader = CustomImageDownloader.shared
|
||||||
|
options.backgroundDecode = true
|
||||||
|
options.cacheOriginalImage = true
|
||||||
|
options.scaleFactor = UIScreen.main.scale
|
||||||
|
|
||||||
|
options.processor = CustomImageProcessor(
|
||||||
|
maxSize: imageContext.maxMebibyteSize(),
|
||||||
|
downsampleSize: imageContext.downsampleSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
options.cacheSerializer = CustomCacheSerializer(
|
||||||
|
maxSize: imageContext.maxMebibyteSize(),
|
||||||
|
downsampleSize: imageContext.downsampleSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self {
|
||||||
|
guard let url = fallbackUrl, let key = cacheKey else { return self }
|
||||||
|
let imageResource = ImageResource(downloadURL: url, cacheKey: key)
|
||||||
|
let source = imageResource.convertToSource()
|
||||||
|
options.alternativeSources = [source]
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let MAX_FILE_SIZE = 20_971_520 // 20MiB
|
||||||
|
|
||||||
|
enum ImageContext {
|
||||||
|
case pfp
|
||||||
|
case banner
|
||||||
|
case note
|
||||||
|
|
||||||
|
func maxMebibyteSize() -> Int {
|
||||||
|
switch self {
|
||||||
|
case .pfp:
|
||||||
|
return 5_242_880 // 5Mib
|
||||||
|
case .banner, .note:
|
||||||
|
return 20_971_520 // 20MiB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downsampleSize() -> CGSize {
|
||||||
|
switch self {
|
||||||
|
case .pfp:
|
||||||
|
return CGSize(width: 200, height: 200)
|
||||||
|
case .banner:
|
||||||
|
return CGSize(width: 750, height: 250)
|
||||||
|
case .note:
|
||||||
|
return CGSize(width: 500, height: 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomImageProcessor: ImageProcessor {
|
||||||
|
|
||||||
|
let maxSize: Int
|
||||||
|
let downsampleSize: CGSize
|
||||||
|
|
||||||
|
let identifier = "com.damus.customimageprocessor"
|
||||||
|
|
||||||
|
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .image(_):
|
||||||
|
// This case will never run
|
||||||
|
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||||
|
case .data(let data):
|
||||||
|
|
||||||
|
// Handle large image size
|
||||||
|
if data.count > maxSize {
|
||||||
|
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle SVG image
|
||||||
|
if let dataString = String(data: data, encoding: .utf8),
|
||||||
|
let svg = SVG(dataString) {
|
||||||
|
|
||||||
|
let render = UIGraphicsImageRenderer(size: svg.size)
|
||||||
|
let image = render.image { context in
|
||||||
|
svg.draw(in: context.cgContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.kf.scaled(to: options.scaleFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomCacheSerializer: CacheSerializer {
|
||||||
|
|
||||||
|
let maxSize: Int
|
||||||
|
let downsampleSize: CGSize
|
||||||
|
|
||||||
|
func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? {
|
||||||
|
return DefaultCacheSerializer.default.data(with: image, original: original)
|
||||||
|
}
|
||||||
|
|
||||||
|
func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? {
|
||||||
|
if data.count > maxSize {
|
||||||
|
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultCacheSerializer.default.image(with: data, options: options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomSessionDelegate: SessionDelegate {
|
||||||
|
override func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||||
|
let contentLength = response.expectedContentLength
|
||||||
|
|
||||||
|
// Content-Length header is optional (-1 when missing)
|
||||||
|
if (contentLength != -1 && contentLength > MAX_FILE_SIZE) {
|
||||||
|
return super.urlSession(session, dataTask: dataTask, didReceive: URLResponse(), completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.urlSession(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomImageDownloader: ImageDownloader {
|
||||||
|
|
||||||
|
static let shared = CustomImageDownloader(name: "shared")
|
||||||
|
|
||||||
|
override init(name: String) {
|
||||||
|
super.init(name: name)
|
||||||
|
sessionDelegate = CustomSessionDelegate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,40 +10,23 @@ import Kingfisher
|
|||||||
|
|
||||||
struct InnerBannerImageView: View {
|
struct InnerBannerImageView: View {
|
||||||
|
|
||||||
|
let url: URL?
|
||||||
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
|
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
|
||||||
|
|
||||||
@ObservedObject var imageModel: KFImageModel
|
|
||||||
|
|
||||||
init(url: URL?) {
|
|
||||||
self.imageModel = KFImageModel(
|
|
||||||
url: url,
|
|
||||||
fallbackUrl: nil,
|
|
||||||
maxByteSize: 20_971_520, // 20 MiB
|
|
||||||
downsampleSize: CGSize(width: 750, height: 250)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(uiColor: .systemBackground)
|
Color(uiColor: .systemBackground)
|
||||||
|
|
||||||
if (imageModel.url != nil) {
|
if (url != nil) {
|
||||||
KFAnimatedImage(imageModel.url)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
.imageContext(.banner)
|
||||||
.processingQueue(.dispatch(.global(qos: .background)))
|
|
||||||
.serialize(by: imageModel.serializer)
|
|
||||||
.setProcessor(imageModel.processor)
|
|
||||||
.cacheOriginalImage()
|
|
||||||
.configure { view in
|
.configure { view in
|
||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.placeholder { _ in
|
.placeholder { _ in
|
||||||
Color(uiColor: .secondarySystemBackground)
|
Color(uiColor: .secondarySystemBackground)
|
||||||
}
|
}
|
||||||
.scaleFactor(UIScreen.main.scale)
|
|
||||||
.loadDiskFileSynchronously()
|
|
||||||
.onFailureImage(defaultImage)
|
.onFailureImage(defaultImage)
|
||||||
.id(imageModel.refreshID)
|
|
||||||
} else {
|
} else {
|
||||||
Image(uiImage: defaultImage).resizable()
|
Image(uiImage: defaultImage).resizable()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,24 +33,13 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct InnerProfilePicView: View {
|
struct InnerProfilePicView: View {
|
||||||
|
|
||||||
|
let url: URL?
|
||||||
|
let fallbackUrl: URL?
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
let highlight: Highlight
|
let highlight: Highlight
|
||||||
|
|
||||||
@ObservedObject var imageModel: KFImageModel
|
|
||||||
|
|
||||||
init(url: URL?, fallbackUrl: URL?, pubkey: String, size: CGFloat, highlight: Highlight) {
|
|
||||||
self.pubkey = pubkey
|
|
||||||
self.size = size
|
|
||||||
self.highlight = highlight
|
|
||||||
self.imageModel = KFImageModel(
|
|
||||||
url: url,
|
|
||||||
fallbackUrl: fallbackUrl,
|
|
||||||
maxByteSize: 5_242_880, // 5Mib
|
|
||||||
downsampleSize: CGSize(width: 200, height: 200)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var PlaceholderColor: Color {
|
var PlaceholderColor: Color {
|
||||||
return id_to_color(pubkey)
|
return id_to_color(pubkey)
|
||||||
}
|
}
|
||||||
@@ -67,26 +56,16 @@ struct InnerProfilePicView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
Color(uiColor: .systemBackground)
|
Color(uiColor: .systemBackground)
|
||||||
|
|
||||||
KFAnimatedImage(imageModel.url)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
.imageContext(.pfp)
|
||||||
.processingQueue(.dispatch(.global(qos: .background)))
|
.onFailure(fallbackUrl: fallbackUrl, cacheKey: url?.absoluteString)
|
||||||
.cancelOnDisappear(true)
|
.cancelOnDisappear(true)
|
||||||
.backgroundDecode()
|
|
||||||
.serialize(by: imageModel.serializer)
|
|
||||||
.setProcessor(imageModel.processor)
|
|
||||||
.cacheOriginalImage()
|
|
||||||
.scaleFactor(UIScreen.main.scale)
|
|
||||||
.configure { view in
|
.configure { view in
|
||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.placeholder { _ in
|
.placeholder { _ in
|
||||||
Placeholder
|
Placeholder
|
||||||
}
|
}
|
||||||
.onFailure { error in
|
|
||||||
if error.isTaskCancelled { return }
|
|
||||||
imageModel.downloadFailed()
|
|
||||||
}
|
|
||||||
.id(imageModel.refreshID)
|
|
||||||
}
|
}
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
|||||||
@@ -9,20 +9,11 @@ import Kingfisher
|
|||||||
|
|
||||||
private struct ImageContainerView: View {
|
private struct ImageContainerView: View {
|
||||||
|
|
||||||
@ObservedObject var imageModel: KFImageModel
|
let url: URL?
|
||||||
|
|
||||||
@State private var image: UIImage?
|
@State private var image: UIImage?
|
||||||
@State private var showShareSheet = false
|
@State private var showShareSheet = false
|
||||||
|
|
||||||
init(url: URL?) {
|
|
||||||
self.imageModel = KFImageModel(
|
|
||||||
url: url,
|
|
||||||
fallbackUrl: nil,
|
|
||||||
maxByteSize: 2000000, // 2 MB
|
|
||||||
downsampleSize: CGSize(width: 400, height: 400)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ImageHandler: ImageModifier {
|
private struct ImageHandler: ImageModifier {
|
||||||
@Binding var handler: UIImage?
|
@Binding var handler: UIImage?
|
||||||
|
|
||||||
@@ -34,25 +25,16 @@ private struct ImageContainerView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
KFAnimatedImage(imageModel.url)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
.imageContext(.pfp)
|
||||||
.processingQueue(.dispatch(.global(qos: .background)))
|
|
||||||
.cacheOriginalImage()
|
|
||||||
.configure { view in
|
.configure { view in
|
||||||
view.framePreloadCount = 1
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.scaleFactor(UIScreen.main.scale)
|
|
||||||
.loadDiskFileSynchronously()
|
|
||||||
.fade(duration: 0.1)
|
|
||||||
.imageModifier(ImageHandler(handler: $image))
|
.imageModifier(ImageHandler(handler: $image))
|
||||||
.onFailure { _ in
|
|
||||||
imageModel.downloadFailed()
|
|
||||||
}
|
|
||||||
.id(imageModel.refreshID)
|
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
|
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
|
||||||
.sheet(isPresented: $showShareSheet) {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
ShareSheet(activityItems: [imageModel.url])
|
ShareSheet(activityItems: [url])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user