diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index 3e8f8fdc..04e7da74 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -273,7 +273,6 @@
DB49A63D25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A63C25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift */; };
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; };
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; };
- DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; };
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; };
DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; };
@@ -1146,7 +1145,6 @@
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */,
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */,
- DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */,
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */,
@@ -2677,7 +2675,6 @@
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
2D42FF6025C8177C004A627A /* ActiveLabel */,
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
- DB5086B725CC0D6400C2C187 /* Kingfisher */,
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */,
2D939AC725EE14620076FA61 /* CropViewController */,
DB9A487D2603456B008B817C /* UITextView+Placeholder */,
@@ -2871,7 +2868,6 @@
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */,
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
- DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */,
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */,
2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */,
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */,
@@ -4801,14 +4797,6 @@
minimumVersion = 4.1.0;
};
};
- DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
- isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/onevcat/Kingfisher.git";
- requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 6.1.0;
- };
- };
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
@@ -4938,11 +4926,6 @@
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
productName = AlamofireImage;
};
- DB5086B725CC0D6400C2C187 /* Kingfisher */ = {
- isa = XCSwiftPackageProductDependency;
- package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */;
- productName = Kingfisher;
- };
DB68050F2637D0F800430867 /* KeychainAccess */ = {
isa = XCSwiftPackageProductDependency;
package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
index da1d80e9..f1135b12 100644
--- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -12,7 +12,7 @@
CoreDataStack.xcscheme_^#shared#^_
orderHint
- 21
+ 20
Mastodon - ASDK.xcscheme_^#shared#^_
@@ -37,7 +37,7 @@
NotificationService.xcscheme_^#shared#^_
orderHint
- 20
+ 21
SuppressBuildableAutocreation
diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 8414b940..817624a3 100644
--- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -114,8 +114,8 @@
"repositoryURL": "https://github.com/TwidereProject/MetaTextView.git",
"state": {
"branch": null,
- "revision": "5b86b386464be8a6da5383aa714c458c07da6c01",
- "version": "1.2.3"
+ "revision": "28e53130d16f12e0eeb479d83b77a0a718ef2088",
+ "version": "1.2.4"
}
},
{
diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift
index 267f47b7..820f9d4b 100644
--- a/Mastodon/Diffiable/Section/NotificationSection.swift
+++ b/Mastodon/Diffiable/Section/NotificationSection.swift
@@ -11,6 +11,7 @@ import CoreDataStack
import Foundation
import MastodonSDK
import UIKit
+import Nuke
enum NotificationSection: Equatable, Hashable {
case main
@@ -72,10 +73,13 @@ extension NotificationSection {
}
.store(in: &cell.disposeBag)
if let url = notification.account.avatarImageURL() {
- cell.avatarImageView.af.setImage(
- withURL: url,
- placeholderImage: UIImage.placeholder(color: .systemFill),
- imageTransition: .crossDissolve(0.2)
+ cell.avatarImageViewTask = Nuke.loadImage(
+ with: url,
+ options: ImageLoadingOptions(
+ placeholder: UIImage.placeholder(color: .systemFill),
+ transition: .fadeIn(duration: 0.2)
+ ),
+ into: cell.avatarImageView
)
}
cell.avatarImageView.gesture().sink { [weak cell] _ in
@@ -113,10 +117,13 @@ extension NotificationSection {
cell.actionLabel.text = actionText + " ยท " + timeText
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
if let url = notification.account.avatarImageURL() {
- cell.avatarImageView.af.setImage(
- withURL: url,
- placeholderImage: UIImage.placeholder(color: .systemFill),
- imageTransition: .crossDissolve(0.2)
+ cell.avatarImageViewTask = Nuke.loadImage(
+ with: url,
+ options: ImageLoadingOptions(
+ placeholder: UIImage.placeholder(color: .systemFill),
+ transition: .fadeIn(duration: 0.2)
+ ),
+ into: cell.avatarImageView
)
}
cell.avatarImageView.gesture().sink { [weak cell] _ in
diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift
index dd244eba..c5995124 100644
--- a/Mastodon/Diffiable/Section/StatusSection.swift
+++ b/Mastodon/Diffiable/Section/StatusSection.swift
@@ -547,7 +547,13 @@ extension StatusSection {
// name
let author = (status.reblog ?? status).author
let nameContent = author.displayNameWithFallback
- cell.statusView.nameLabel.configure(content: nameContent, emojiDict: author.emojiDict)
+ MastodonStatusContent.parseResult(content: nameContent, emojiDict: author.emojiDict)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak cell] parseResult in
+ guard let cell = cell else { return }
+ cell.statusView.nameLabel.configure(contentParseResult: parseResult)
+ }
+ .store(in: &cell.disposeBag)
// username
cell.statusView.usernameLabel.text = "@" + author.acct
// avatar
@@ -571,9 +577,10 @@ extension StatusSection {
) {
// set content
do {
+ let status = status.reblog ?? status
let content = MastodonContent(
- content: (status.reblog ?? status).content,
- emojis: (status.reblog ?? status).emojiMeta
+ content: status.content,
+ emojis: status.emojiMeta
)
let metaContent = try MastodonMetaContent.convert(document: content)
cell.statusView.contentMetaText.configure(content: metaContent)
@@ -648,46 +655,19 @@ extension StatusSection {
let isSingleMosaicLayout = mosaics.count == 1
- // set link preview
-// cell.statusView.linkPreview.isHidden = true
-//
-// var _firstURL: URL? = {
-// for entity in cell.statusView.activeTextLabel.activeEntities {
-// guard case let .url(_, _, url, _) = entity.type else { continue }
-// return URL(string: url)
-// }
-// return nil
-// }()
-//
-// if let url = _firstURL {
-// Future { promise in
-// LPMetadataProvider().startFetchingMetadata(for: url) { meta, error in
-// if let error = error {
-// promise(.failure(error))
-// } else {
-// promise(.success(meta))
-// }
-// }
-// }
-// .receive(on: RunLoop.main)
-// .sink { _ in
-// // do nothing
-// } receiveValue: { [weak cell] meta in
-// guard let meta = meta else { return }
-// guard let cell = cell else { return }
-// cell.statusView.linkPreview.metadata = meta
-// cell.statusView.linkPreview.isHidden = false
-// }
-// .store(in: &cell.disposeBag)
-// }
-
// set image
let imageSize = CGSize(
width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale,
height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale
)
+ let url: URL? = {
+ if UIDevice.current.userInterfaceIdiom == .phone {
+ return meta.previewURL ?? meta.url
+ }
+ return meta.url
+ }()
let request = ImageRequest(
- url: meta.url,
+ url: url,
processors: [
ImageProcessors.Resize(
size: imageSize,
diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift
index b4f89e71..ebb82655 100644
--- a/Mastodon/Extension/ActiveLabel.swift
+++ b/Mastodon/Extension/ActiveLabel.swift
@@ -57,6 +57,15 @@ extension ActiveLabel {
}
+extension ActiveLabel {
+ func configure(text: String) {
+ attributedText = nil
+ activeEntities.removeAll()
+ self.text = text
+ accessibilityLabel = text
+ }
+}
+
extension ActiveLabel {
/// status content
diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift
index 1e52f150..d19463a8 100755
--- a/Mastodon/Helper/MastodonStatusContent.swift
+++ b/Mastodon/Helper/MastodonStatusContent.swift
@@ -15,7 +15,7 @@ enum MastodonStatusContent {
typealias EmojiShortcode = String
typealias EmojiDict = [EmojiShortcode: URL]
- static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive)
+ static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive, attributes: .concurrent)
static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher {
return Future { promise in
diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift
index a9e1898a..5d6e81d5 100644
--- a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift
+++ b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift
@@ -14,27 +14,27 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
// prefetch reply status
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
let domain = activeMastodonAuthenticationBox.domain
-
- var statusObjectIDs: [NSManagedObjectID] = []
- for item in items(indexPaths: indexPaths) {
- switch item {
- case .homeTimelineIndex(let objectID, _):
- let homeTimelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
- statusObjectIDs.append(homeTimelineIndex.status.objectID)
- case .status(let objectID, _):
- statusObjectIDs.append(objectID)
- default:
- continue
- }
- }
-
- let backgroundManagedObjectContext = context.backgroundManagedObjectContext
- backgroundManagedObjectContext.perform { [weak self] in
+ let items = self.items(indexPaths: indexPaths)
+
+ let managedObjectContext = context.managedObjectContext
+ managedObjectContext.perform { [weak self] in
guard let self = self else { return }
- for objectID in statusObjectIDs {
- let status = backgroundManagedObjectContext.object(with: objectID) as! Status
-
- // fetch in-reply info if needs
+
+ var statuses: [Status] = []
+ for item in items {
+ switch item {
+ case .homeTimelineIndex(let objectID, _):
+ guard let homeTimelineIndex = try? managedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else { continue }
+ statuses.append(homeTimelineIndex.status)
+ case .status(let objectID, _):
+ guard let status = try? managedObjectContext.existingObject(with: objectID) as? Status else { continue }
+ statuses.append(status)
+ default:
+ continue
+ }
+ }
+
+ for status in statuses {
if let replyToID = status.inReplyToID, status.replyTo == nil {
self.context.statusPrefetchingService.prefetchReplyTo(
domain: domain,
@@ -44,12 +44,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
authorizationBox: activeMastodonAuthenticationBox
)
}
-
-// self.context.statusContentCacheService.prefetch(
-// content: (status.reblog ?? status).content,
-// emojiDict: (status.reblog ?? status).emojiDict
-// )
- }
- }
- }
+ } // end for in
+ } // end context.perform
+ } // end func
}
diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift
index 24736480..c8ccfdd3 100644
--- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift
+++ b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift
@@ -18,10 +18,6 @@ import MastodonSDK
import AlamofireImage
import AsyncDisplayKit
-#if DEBUG
-import GDPerformanceView_Swift
-#endif
-
final class AsyncHomeTimelineViewController: ASDKViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
@@ -107,7 +103,6 @@ extension AsyncHomeTimelineViewController {
#if DEBUG
// long press to trigger debug menu
settingBarButtonItem.menu = debugMenu
- PerformanceMonitor.shared().delegate = self
#else
settingBarButtonItem.target = self
settingBarButtonItem.action = #selector(AsyncHomeTimelineViewController.settingBarButtonItemPressed(_:))
@@ -548,13 +543,6 @@ extension AsyncHomeTimelineViewController: StatusTableViewControllerNavigateable
}
}
-#if DEBUG
-extension AsyncHomeTimelineViewController: PerformanceMonitorDelegate {
- func performanceMonitor(didReport performanceReport: PerformanceReport) {
- // print(performanceReport)
- }
-}
-#endif
// MARK: - ASTableDelegate
extension AsyncHomeTimelineViewController: ASTableDelegate {
diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift
index fdbbfba9..d22c52be 100644
--- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift
+++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift
@@ -77,7 +77,12 @@ final class HomeTimelineViewModel: NSObject {
let fetchRequest = HomeTimelineIndex.sortedFetchRequest
fetchRequest.fetchBatchSize = 20
fetchRequest.returnsObjectsAsFaults = false
- fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(HomeTimelineIndex.status)]
+ fetchRequest.relationshipKeyPathsForPrefetching = [
+ #keyPath(HomeTimelineIndex.status),
+ #keyPath(HomeTimelineIndex.status.author),
+ #keyPath(HomeTimelineIndex.status.reblog),
+ #keyPath(HomeTimelineIndex.status.reblog.author),
+ ]
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: context.managedObjectContext,
diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift
index aa11e494..05f2ce70 100644
--- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift
+++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift
@@ -8,11 +8,12 @@
import os.log
import func AVFoundation.AVMakeRect
import UIKit
+import FLAnimatedImage
final class MediaPreviewImageView: UIScrollView {
- let imageView: UIImageView = {
- let imageView = UIImageView()
+ let imageView: FLAnimatedImageView = {
+ let imageView = FLAnimatedImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
@@ -120,7 +121,9 @@ extension MediaPreviewImageView {
}
}()
imageView.frame = CGRect(origin: .zero, size: imageViewSize)
- imageView.image = image
+ if imageView.image == nil {
+ imageView.image = image
+ }
contentSize = imageViewSize
contentInset = imageContentInset
diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift
index e1f2736f..5ec82db9 100644
--- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift
+++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift
@@ -8,6 +8,7 @@
import os.log
import UIKit
import Combine
+import Nuke
protocol MediaPreviewImageViewControllerDelegate: AnyObject {
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer)
@@ -68,17 +69,36 @@ extension MediaPreviewImageViewController {
let previewImageViewContextMenuInteraction = UIContextMenuInteraction(delegate: self)
previewImageView.addInteraction(previewImageViewContextMenuInteraction)
-
- viewModel.image
- .receive(on: RunLoop.main) // use RunLoop prevent set image during zooming (TODO: handle transitioning state)
- .sink { [weak self] image in
- guard let self = self else { return }
- guard let image = image else { return }
- self.previewImageView.imageView.image = image
- self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true)
- self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
+
+ switch viewModel.item {
+ case .local(let meta):
+ self.previewImageView.imageView.image = meta.image
+ self.previewImageView.setup(image: meta.image, container: self.previewImageView, forceUpdate: true)
+ self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
+ case .status(let meta):
+ Nuke.loadImage(
+ with: meta.url,
+ into: self.previewImageView.imageView
+ ) { result in
+ switch result {
+ case .failure(let error):
+ break
+ case .success(let response):
+ self.previewImageView.setup(image: response.image, container: self.previewImageView, forceUpdate: true)
+ self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
+ }
}
- .store(in: &disposeBag)
+ }
+// viewModel.image
+// .receive(on: RunLoop.main) // use RunLoop prevent set image during zooming (TODO: handle transitioning state)
+// .sink { [weak self] image in
+// guard let self = self else { return }
+// guard let image = image else { return }
+// self.previewImageView.imageView.image = image
+// self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true)
+// self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
+// }
+// .store(in: &disposeBag)
}
}
diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift
index c9afac8c..a6163a8c 100644
--- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift
+++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift
@@ -8,9 +8,11 @@
import os.log
import UIKit
import Combine
-import AlamofireImage
+import Nuke
class MediaPreviewImageViewModel {
+
+ var disposeBag = Set()
// input
let item: ImagePreviewItem
@@ -25,16 +27,20 @@ class MediaPreviewImageViewModel {
self.altText = meta.altText
let url = meta.url
- ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in
- guard let self = self else { return }
- switch response.result {
- case .failure(let error):
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
- case .success(let image):
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
- self.image.value = image
+
+ ImagePipeline.shared.imagePublisher(with: url)
+ .sink { completion in
+ switch completion {
+ case .failure(let error):
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
+ case .finished:
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
+ }
+ } receiveValue: { [weak self] response in
+ guard let self = self else { return }
+ self.image.value = response.image
}
- })
+ .store(in: &disposeBag)
}
init(meta: LocalImagePreviewMeta) {
diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift
index d2231d4f..83fa5009 100644
--- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift
+++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift
@@ -11,6 +11,8 @@ import UIKit
import ActiveLabel
import MetaTextView
import Meta
+import FLAnimatedImage
+import Nuke
final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
static let actionImageBorderWidth: CGFloat = 2
@@ -18,9 +20,10 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
var disposeBag = Set()
var pollCountdownSubscription: AnyCancellable?
var delegate: NotificationTableViewCellDelegate?
-
+
+ var avatarImageViewTask: ImageTask?
let avatarImageView: UIImageView = {
- let imageView = UIImageView()
+ let imageView = FLAnimatedImageView()
imageView.layer.cornerRadius = 4
imageView.layer.cornerCurve = .continuous
imageView.clipsToBounds = true
@@ -88,12 +91,12 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
override func prepareForReuse() {
super.prepareForReuse()
- avatarImageView.af.cancelImageRequest()
+ avatarImageViewTask?.cancel()
+ avatarImageViewTask = nil
statusView.updateContentWarningDisplay(isHidden: true, animated: false)
statusView.pollTableView.dataSource = nil
statusView.playerContainerView.reset()
statusView.playerContainerView.isHidden = true
-
disposeBag.removeAll()
}
diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift
index 15fa21c6..30b4ce1b 100644
--- a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift
+++ b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift
@@ -12,6 +12,8 @@ import UIKit
import Meta
import MetaTextView
import ActiveLabel
+import FLAnimatedImage
+import Nuke
protocol NotificationTableViewCellDelegate: AnyObject {
var context: AppContext! { get }
@@ -37,9 +39,10 @@ final class NotificationTableViewCell: UITableViewCell {
var disposeBag = Set()
var delegate: NotificationTableViewCellDelegate?
-
+
+ var avatarImageViewTask: ImageTask?
let avatarImageView: UIImageView = {
- let imageView = UIImageView()
+ let imageView = FLAnimatedImageView()
imageView.layer.cornerRadius = 4
imageView.layer.cornerCurve = .continuous
imageView.clipsToBounds = true
@@ -112,7 +115,8 @@ final class NotificationTableViewCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
- avatarImageView.af.cancelImageRequest()
+ avatarImageViewTask?.cancel()
+ avatarImageViewTask = nil
disposeBag.removeAll()
}
diff --git a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
index 2a5ba492..28b13e2f 100644
--- a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
+++ b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
@@ -8,19 +8,27 @@
import func AVFoundation.AVMakeRect
import UIKit
import Combine
+import Nuke
+import FLAnimatedImage
final class ContextMenuImagePreviewViewController: UIViewController {
var disposeBag = Set()
var viewModel: ContextMenuImagePreviewViewModel!
-
+
+ var imageTask: ImageTask?
let imageView: UIImageView = {
- let imageView = UIImageView()
+ let imageView = FLAnimatedImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
+
+ deinit {
+ imageTask?.cancel()
+ imageTask = nil
+ }
}
@@ -47,12 +55,13 @@ extension ContextMenuImagePreviewViewController {
.sink { [weak self] url in
guard let self = self else { return }
guard let url = url else { return }
- self.imageView.af.setImage(
- withURL: url,
- placeholderImage: self.viewModel.thumbnail,
- imageTransition: .crossDissolve(0.2),
- runImageTransitionIfCached: true,
- completion: nil
+ self.imageTask = Nuke.loadImage(
+ with: url,
+ options: ImageLoadingOptions(
+ placeholder: self.viewModel.thumbnail,
+ transition: .fadeIn(duration: 0.2)
+ ),
+ into: self.imageView
)
}
.store(in: &disposeBag)
diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift
index 2b51f028..7ed828fa 100644
--- a/Mastodon/Scene/Share/View/Content/StatusView.swift
+++ b/Mastodon/Scene/Share/View/Content/StatusView.swift
@@ -95,7 +95,12 @@ final class StatusView: UIView {
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
return view
}()
- let avatarImageView: UIImageView = FLAnimatedImageView()
+ let avatarImageView: UIImageView = {
+ let imageView = FLAnimatedImageView()
+ imageView.layer.shouldRasterize = true
+ imageView.layer.rasterizationScale = UIScreen.main.scale
+ return imageView
+ }()
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
let nameLabel: ActiveLabel = {
@@ -217,6 +222,7 @@ final class StatusView: UIView {
metaText.textView.textContainer.lineFragmentPadding = 0
metaText.textView.textContainerInset = .zero
metaText.textView.layer.masksToBounds = false
+
let paragraphStyle: NSMutableParagraphStyle = {
let style = NSMutableParagraphStyle()
style.lineSpacing = 5
@@ -234,7 +240,7 @@ final class StatusView: UIView {
]
return metaText
}()
-
+
private let headerInfoLabelTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
var isRevealing = true
@@ -400,11 +406,6 @@ extension StatusView {
statusContainerStackView.addArrangedSubview(contentMetaText.textView)
contentMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical)
- // TODO:
- // link preview
- // statusContainerStackView.addArrangedSubview(linkPreview)
- // linkPreview.setContentHuggingPriority(.defaultHigh, for: .vertical)
-
// image
statusContainerStackView.addArrangedSubview(statusMosaicImageViewContainer)
diff --git a/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift b/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift
index 965d710d..d16a952f 100644
--- a/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift
+++ b/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift
@@ -25,7 +25,7 @@ final class AvatarStackContainerButton: UIControl {
static let maskOffset: CGFloat = 2
// UIControl.Event - Application: 0x0F000000
- static let primaryAction = UIControl.Event(rawValue: 1 << 25) // 0x01000000
+ static let primaryAction = UIControl.Event(rawValue: 1 << 25) // 0x01000000
var primaryActionState: UIControl.State = .normal
let topLeadingAvatarStackedImageView = AvatarStackedImageView()
@@ -46,6 +46,12 @@ final class AvatarStackContainerButton: UIControl {
extension AvatarStackContainerButton {
private func _init() {
+ topLeadingAvatarStackedImageView.layer.shouldRasterize = true
+ topLeadingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
+
+ bottomTrailingAvatarStackedImageView.layer.shouldRasterize = true
+ bottomTrailingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
+
topLeadingAvatarStackedImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(topLeadingAvatarStackedImageView)
NSLayoutConstraint.activate([
diff --git a/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift b/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift
index 265ce245..5ceb8781 100644
--- a/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift
+++ b/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift
@@ -23,7 +23,7 @@ struct MosaicImageViewModel {
continue
}
let mosaicMeta = MosaicMeta(
- priviewURL: element.previewURL.flatMap { URL(string: $0) },
+ previewURL: element.previewURL.flatMap { URL(string: $0) },
url: url,
size: CGSize(width: width, height: height),
blurhash: element.blurhash,
@@ -39,7 +39,7 @@ struct MosaicImageViewModel {
struct MosaicMeta {
static let edgeMaxLength: CGFloat = 20
- let priviewURL: URL?
+ let previewURL: URL?
let url: URL
let size: CGSize
let blurhash: String?
diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
index b154d17c..f773115d 100644
--- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
+++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
@@ -8,7 +8,6 @@
import os.log
import Foundation
import GameplayKit
-import Kingfisher
import MastodonSDK
extension MastodonAttachmentService {
diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
index ede5c64b..cafa3145 100644
--- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
+++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
@@ -9,7 +9,6 @@ import os.log
import UIKit
import Combine
import PhotosUI
-import Kingfisher
import GameplayKit
import MobileCoreServices
import MastodonSDK
diff --git a/Mastodon/State/AppContext.swift b/Mastodon/State/AppContext.swift
index ef758951..f1de6dd7 100644
--- a/Mastodon/State/AppContext.swift
+++ b/Mastodon/State/AppContext.swift
@@ -11,7 +11,6 @@ import Combine
import CoreData
import CoreDataStack
import AlamofireImage
-import Kingfisher
class AppContext: ObservableObject {
@@ -124,7 +123,6 @@ extension AppContext {
func purgeCache() -> AnyPublisher {
Publishers.MergeMany([
AppContext.purgeAlamofireImageCache(),
- AppContext.purgeKingfisherCache(),
AppContext.purgeTemporaryDirectory(),
])
.reduce(0, +)
@@ -146,29 +144,6 @@ extension AppContext {
.eraseToAnyPublisher()
}
- private static func purgeKingfisherCache() -> AnyPublisher {
- Future { promise in
- KingfisherManager.shared.cache.calculateDiskStorageSize { result in
- switch result {
- case .success(let diskBytes):
- KingfisherManager.shared.cache.clearCache()
- KingfisherManager.shared.cache.calculateDiskStorageSize { currentResult in
- switch currentResult {
- case .success(let currentDiskBytes):
- let purgedDiskBytes = max(0, Int(diskBytes) - Int(currentDiskBytes))
- promise(.success(purgedDiskBytes))
- case .failure:
- promise(.success(0))
- }
- }
- case .failure:
- promise(.success(0))
- }
- }
- }
- .eraseToAnyPublisher()
- }
-
private static func purgeTemporaryDirectory() -> AnyPublisher {
Future { promise in
AppContext.purgeCacheWorkingQueue.async {