Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Branch 25.7 #23923

Merged
merged 217 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 214 commits
Commits
Show all changes
217 commits
Select commit Hold shift + click to select a range
5812760
Rename ImageLoadingController
kean Dec 23, 2024
d807dfe
Add LightboxViewController to replace WPImageViewController
kean Dec 24, 2024
0d05ae8
Integrate LightboxViewController in Reader
kean Dec 24, 2024
1407116
Add Media support in LightboxViewController
kean Dec 24, 2024
79c49ae
Add convenience init to LightboxViewController
kean Dec 24, 2024
246b122
Integrate LightboxViewController in SiteMedia
kean Dec 24, 2024
6ed4454
Integrate LightboxViewController in ReaderDetailsCoordinator (cover i…
kean Dec 24, 2024
95399b0
INtegrate in DefaultContentCoordinator
kean Dec 24, 2024
b3a84ac
Integrate LightboxViewController in Guteberg
kean Dec 24, 2024
8fbeb66
Integrate LightboxViewController in ExternalMediaPickerViewController
kean Dec 24, 2024
8f7dd16
Integrate LightboxViewController in PostSettingsViewController (featu…
kean Dec 24, 2024
8096de9
Remove FeaturedImageViewController (ObjC)
kean Dec 24, 2024
88c7b13
Rewrite PostFeaturedImageCell
kean Dec 24, 2024
da7b29a
Integrate LightboxViewController in ReaderCommentsViewController
kean Dec 24, 2024
3354d85
Update WPRichTextImage to use AsyncImageView
kean Dec 24, 2024
c3993e0
Automatically pick thumbnail when available
kean Dec 24, 2024
36b392e
Remove WPImageViewController
kean Dec 24, 2024
342e63e
Update release notes
kean Dec 24, 2024
d613c05
Remove ImageLoader
kean Dec 24, 2024
2ac4c2e
Remove ImageDimensionParser
kean Dec 24, 2024
5141337
Update MediaItemHeaderView to use AsyncImageView instead of CachedAni…
kean Dec 24, 2024
a3adfee
Fix code formatting in RichTextView
kean Dec 24, 2024
61871d0
Update AnimatedGifAttachmentViewProvider to use GIFImageView directly
kean Dec 24, 2024
4b3f4ef
Remove SolidColorActivityIndicator
kean Dec 24, 2024
79b3a0a
Remove CachedAnimatedImageView
kean Dec 24, 2024
c96c844
Remove GIFPlaybackStrategy
kean Dec 24, 2024
3f270d2
Update EditorMediaUtility to use ImageDownloader directly (without Au…
kean Dec 24, 2024
dca2548
Remove AuthenticatedImageDownload
kean Dec 24, 2024
f9a2315
Update MediaExternalExporter to use ImageDownloader for downloading G…
kean Dec 24, 2024
8ad55b8
Remove AnimatedImageCache
kean Dec 24, 2024
f013a31
Remove remaining AlamofireImage usages from the anouncement cells
kean Dec 24, 2024
f27c41d
Remove AlamofireImageCacheAdapter
kean Dec 24, 2024
129bc3e
Remove AlamofireImage
kean Dec 24, 2024
eece5e9
Add ImagePrefetcher
kean Dec 30, 2024
49df350
Update releaes notes
kean Dec 30, 2024
1e5f0e8
Add ImageRequest support in AsyncImageView
kean Dec 30, 2024
023b3cd
Add ImageSize
kean Dec 30, 2024
f352225
Fix an issue with blogging reminders flow not being shown after publi…
kean Dec 30, 2024
afdfd05
Remove unused LightNavigationController
kean Dec 30, 2024
af4ba9c
Remove BottomSheetViewController usage from BloggingReminders flow
kean Dec 30, 2024
c9dca5f
Simplify BloggingRemindersFlowIntroViewController
kean Dec 31, 2024
956a3f0
Add SpacerView
kean Dec 31, 2024
a49d006
Add BottomToolbarView
kean Dec 31, 2024
6431474
Fix notice covering the blogging reminders fow
kean Dec 31, 2024
79334a2
Replace FancyButton
kean Dec 31, 2024
9c0bed9
Add close button to BloggingRemindersFlowSettingsViewController
kean Dec 31, 2024
882df62
Fix BloggingRemindersTimeSelectionViewController presentation
kean Dec 31, 2024
581a10a
Remove FancyButton from BloggingRemindersPushPromptViewController
kean Dec 31, 2024
9f81f7f
Remove dismiss button (it now shows back)
kean Dec 31, 2024
1d56a16
Update BloggingRemindersPushPromptViewController layout
kean Dec 31, 2024
a5d4fc2
Remove FancyButton from BloggingRemindersFlowCompletionViewController
kean Dec 31, 2024
94dd45f
Update BloggingRemindersFlowCompletionViewController layout
kean Dec 31, 2024
7bbd838
Update releaes notes
kean Dec 31, 2024
c1c70e1
Fix typo in release notes
kean Dec 31, 2024
60a9e23
Fix compliance popover accessibility settings
kean Dec 31, 2024
a1786a4
Fix an issue with compliance popover not dismissing
kean Dec 31, 2024
195e8e8
Update release notes
kean Dec 31, 2024
f91c0d7
Remove unused CircularProgressView extensions
kean Dec 31, 2024
ae33151
Remove BottomSheetViewController usage from JetpackBrandingCoordinator
kean Jan 2, 2025
c97bbdd
Remove ottomSheetViewControllerTests
kean Jan 2, 2025
927616a
Remove BottomSheetViewController
kean Jan 2, 2025
2aa3bff
Remove DrawerPresentationController
kean Jan 2, 2025
8b886a3
Update release notes
kean Jan 2, 2025
1782da1
Add Share action to the site link on dashboard
kean Jan 2, 2025
92d446f
Remove duplicated Share actions
kean Jan 2, 2025
8921783
Remove duplicated Strings.ok
kean Jan 2, 2025
57dc107
Update release notes
kean Jan 2, 2025
86ee87b
Fix layout issues in Privacy Settings
kean Jan 2, 2025
9038395
Add assertion
kean Jan 2, 2025
df6f938
Update release notes
kean Jan 2, 2025
b9de1da
Rename WordPressMedia to AsyncImageKit
kean Jan 2, 2025
cdfbd20
Rename WordPressMedia (#23937)
kean Jan 2, 2025
c7b0a49
Remove MediaHost from AsyncImageKit
kean Jan 2, 2025
4d8d512
Move ImageDownloader.shared to AsyncImageKit
kean Jan 2, 2025
7f45fec
Move AsyncImageView and other related types to AsyncImageKit
kean Jan 2, 2025
90b1166
Fix unit tests
kean Jan 2, 2025
2c17896
Move AsyncImageView to AsyncImageKit (#23938)
kean Jan 2, 2025
330ebaf
Cleanup MediaHost initializers
kean Jan 2, 2025
58ec973
Optimize account lookup
kean Jan 2, 2025
ebbeb8b
Fix MediaHostTests
kean Jan 2, 2025
221386f
Cleanup MediaHost initializers (#23939)
kean Jan 2, 2025
42aea77
Fix crash in ReaderDetailFeaturedImageView
kean Jan 3, 2025
14b5c56
Fix RTL support in WebKitViewController
kean Jan 3, 2025
eb7bed6
Use semantic back/forward chevrons in other places
kean Jan 3, 2025
2b4ff05
Update StatsBaseCell
kean Jan 3, 2025
c227ab4
Update SiteStatsTableHeaderView
kean Jan 3, 2025
e6883c5
Replace disclosure-chevron and editor-chevron-left
kean Jan 3, 2025
379b156
Fix remainig incorrect chevron usages
kean Jan 3, 2025
5e77775
Remove remainig chevron images
kean Jan 3, 2025
161fc2d
Update release notes
kean Jan 3, 2025
3a85553
Fix separator insets on homepage
kean Jan 3, 2025
cb3816d
Fix incorrect chevron icons direction in RTL languages (#23940)
kean Jan 3, 2025
eab7835
Fix an issue with clear navigation bar background in revision browser
kean Jan 3, 2025
bf01b8f
Fix an issue with clear navigation bar background in revision browser
kean Jan 3, 2025
a337327
Fix toolbar inset to safe area in revision browser
kean Jan 3, 2025
56ef3a7
Modernize menus and stuff
kean Jan 3, 2025
87ff4b6
Fix an issue with clear navigation bar background in revision browser…
kean Jan 3, 2025
7b36ced
Fix an issue with clear navigation bar background in revision browser…
kean Jan 3, 2025
8b02a8e
Fix MediaRequestAuthenticatorTests
kean Jan 3, 2025
c74a857
Remove preflight connection check when sending replies (can be laggin…
kean Jan 3, 2025
b1709b7
Fix an issue with comments disppearing if request fails
kean Jan 3, 2025
d847e8a
Update other screens using TextView
kean Jan 3, 2025
cb47b04
Update release notes
kean Jan 3, 2025
309ddbc
Fix an issue with comments being lost on request failure (#23942)
kean Jan 3, 2025
ccbdbfb
Fix formatting
kean Jan 3, 2025
13bcd39
Fix an issue with referrers showing invalid icons
kean Jan 3, 2025
2f22b2f
Update release notes
kean Jan 3, 2025
fe31a0d
Fix an issue with Referrers in Stats showing invalid icons (#23943)
kean Jan 3, 2025
4c2f229
Remove some of the scenarios where isInternetConnected used
kean Jan 4, 2025
565a34b
Update site menu style on iPhone
kean Jan 6, 2025
c02b29b
Update release notes
kean Jan 6, 2025
fcc9cf5
Update site menu style on iPhone (#23944)
kean Jan 6, 2025
26058c7
Integrate zoom transitions in Theme browser
kean Jan 6, 2025
babc529
Update release notes
kean Jan 6, 2025
ff930aa
Integrate zoom transitions in Theme browser (#23945)
kean Jan 6, 2025
af9dd41
Fix tint colors in wpios
kean Jan 6, 2025
ff83911
Remove UIAppColor.brand
kean Jan 6, 2025
2dbd0fc
Fix tint color in wpios (#23946)
kean Jan 6, 2025
8cdd43e
Enable zoom transitions in Reader (iPad)
kean Jan 6, 2025
7584f56
Update release notes
kean Jan 6, 2025
e6f37bd
Integrate zoom transitions in Reader (#23947)
kean Jan 6, 2025
d86c342
Remove unused isVisibleInScrollView
kean Jan 6, 2025
40f7142
Enable toolbar hiding on iPad
kean Jan 6, 2025
fe68ae3
Fix ReaderDetailFeaturedImageView gradienet showing up when no image …
kean Jan 6, 2025
4b741ea
Enable toolbar hiding on iPad too (#23948)
kean Jan 6, 2025
99b0ba2
Fix an issue with Publisize options appearing in the prepublishing sh…
kean Jan 6, 2025
9edac5f
Fix an issue with Publisize options appearing in the prepublishing sh…
kean Jan 6, 2025
600ad8f
Fix code formatting and remove unused imports
kean Jan 6, 2025
c966dc6
Move SiteIconView to WordPressUI
kean Jan 6, 2025
a6775eb
Update Share extension to use SiteIconView
kean Jan 6, 2025
7c33ae7
Remove UIImageView+Blavatar
kean Jan 6, 2025
119f4cc
Use firstLetter
kean Jan 6, 2025
0862ef1
Update release notes
kean Jan 6, 2025
6e32673
Fix an issue with site icons cropped in share extensions (#23950)
kean Jan 6, 2025
f95fe7c
Disable universal links support for QR code login
kean Jan 7, 2025
4dc172d
Fix an issue with the confirmation screen shown more than once
kean Jan 7, 2025
f9738ee
Update release notes
kean Jan 7, 2025
406da34
Enable fast deceleration for filters on the Discover tab
kean Jan 7, 2025
9879df0
Update release notes
kean Jan 7, 2025
530faff
Enable fast deceleration for filters on the Discover tab (#23954)
kean Jan 7, 2025
09f7e55
Show selected filter in the Discover navigation bar
kean Jan 7, 2025
5fd1a43
Update release notes
kean Jan 7, 2025
011b671
Merge branch 'christmas-feature-branch' into fix/disable-qr-code-univ…
kean Jan 7, 2025
4b41387
Disable universal links support for QR code login (#23953)
kean Jan 7, 2025
f0e5730
Show selected filter in the Discover navigation bar (#23956)
kean Jan 7, 2025
9e70581
Remove unused makeCreateButtonAnnouncementAlertController
kean Jan 7, 2025
9fe9593
Add scroll-to-top button to Reader
kean Jan 7, 2025
321c8d2
Cleanup
kean Jan 7, 2025
8e0026c
Update design for iPad
kean Jan 7, 2025
6ead7b3
Add analytics
kean Jan 7, 2025
2069c49
Update releaes notes
kean Jan 7, 2025
46d183f
Add scroll-to-top button to Reader streams (#23957)
kean Jan 7, 2025
34775f3
Flatten a nested localized string to avoid `genstrings` failure
mokagio Jan 8, 2025
b491858
Import `WordPressUI` in `SiteIconViewModelTests`
mokagio Jan 8, 2025
ab47169
Add initial MediaPicker implementation
kean Jan 8, 2025
6ddf963
Add initial PostSettingsFeaturedImageCell implementation
kean Jan 8, 2025
3338325
Add configurable MediaPicker content
kean Jan 8, 2025
bcd2738
Add ViewModel to PostSettingsFeaturedImageCell
kean Jan 8, 2025
ba566af
Add reuseIdentifier for featured image cells
kean Jan 8, 2025
dbfb9db
Pass selection from MediaPicker to PostSettingsFeaturedImageViewModel
kean Jan 8, 2025
d23bf0e
Show upload status using PostMediaUploadItemView
kean Jan 8, 2025
6c9f7e7
Rename MediaUploadItemViewModel
kean Jan 8, 2025
b90111e
Add PostSettingsFeaturedImageUploadView to show upload progress
kean Jan 8, 2025
20cae40
Simlify how the app shows media upload status
kean Jan 8, 2025
ff30855
Handle upload failure
kean Jan 8, 2025
495d921
Implement featured image save
kean Jan 8, 2025
c83378b
Add support for showing a selected featured image
kean Jan 8, 2025
8cdce43
Add support for removing featured image
kean Jan 8, 2025
477227a
Simplify lightbox
kean Jan 8, 2025
2ab52b7
Add support for camera as a source
kean Jan 8, 2025
a800eae
Add .siteMedia(blog:) source
kean Jan 8, 2025
842dcfe
Add ImagePlayground source support
kean Jan 8, 2025
0d5640d
Add ImagePlayground support in MediaPicker
kean Jan 8, 2025
7483497
Add free photos and GIFs support to MediaPicker
kean Jan 8, 2025
d2c05f0
Remove unused media upload code from PostSettingsViewController
kean Jan 8, 2025
7fbb68a
Remove WPTableViewActivityCell
kean Jan 8, 2025
4e283d4
Remove WPProgressTableViewCell
kean Jan 8, 2025
5e3dd5a
Remvove unused featured image size
kean Jan 8, 2025
ec09ca8
Remove more unused code
kean Jan 8, 2025
e4554a9
Remove unused code
kean Jan 9, 2025
e8b846b
Add SiteMediaImageView
kean Jan 9, 2025
45b2fcc
Remove unused code
kean Jan 9, 2025
7f4c780
Integrate FeaturedImageDelegate
kean Jan 9, 2025
3d007fe
Fix SiteMediaImage background when loading with spinner
kean Jan 9, 2025
c2523d4
Fix animations
kean Jan 9, 2025
017f72f
Add zoom transition
kean Jan 9, 2025
0e100ad
Add shadow to more menu
kean Jan 9, 2025
7ea1cf4
Make the entire cell tappable
kean Jan 9, 2025
5d13e71
Add View action
kean Jan 9, 2025
3f27e17
Add replace action
kean Jan 9, 2025
a828b2c
Show spinner when replacing an image
kean Jan 9, 2025
b0f3a4b
Remove unused reloadFeaturedImageCell
kean Jan 9, 2025
f44329d
Update release notse
kean Jan 9, 2025
f5ff6d2
Address localization and test build failures (#23960)
kean Jan 9, 2025
cd13f63
Improve featured image flow (#23962)
kean Jan 9, 2025
41cd8ee
Revert "Update site menu style on iPhone"
kean Jan 9, 2025
a49ee2d
Fix an issue with wrong cover images appearing in Reader (#23914)
kean Jan 9, 2025
a045bf1
Point back to wpios-edition
kean Jan 9, 2025
e9b4605
Fix an issue with non-stable order in Posts and Pages in stats (#23915)
kean Jan 9, 2025
67ba286
Fix an issue with a missing "Mark as Unread" button in the More menu …
kean Jan 9, 2025
846199d
Add missing social sharing icons (#23918)
kean Jan 9, 2025
754c960
Update release notes
kean Jan 9, 2025
6deca49
Merge branch 'trunk' into christmas-feature-branch
kean Jan 9, 2025
a07bfb3
Fix build
kean Jan 9, 2025
4cd2be4
Update UI tests
kean Jan 9, 2025
d33d540
Use medium font for main navigation area in Reader to align with Home
kean Jan 9, 2025
39fa791
Remove commented-out code
kean Jan 9, 2025
3a4c671
Add context menus and previews for sites in Reader (#23964)
kean Jan 9, 2025
0ea86d8
Update release notes
kean Jan 9, 2025
f26b3e3
Fix more button color in dark mode
kean Jan 10, 2025
daaa5ad
Fix an issue with fullscreen button in reply view clipped by the notc…
kean Jan 10, 2025
7599361
Fix display of certain topics in Discover recommendations
kean Jan 10, 2025
d7ba61f
Remove "Lazy Images" Jetpack option (#23966)
kean Jan 10, 2025
094a68f
Reoder site actions
kean Jan 10, 2025
f8498ff
Merge branch 'trunk' into christmas-feature-branch
kean Jan 13, 2025
6505d56
Update release notes
kean Jan 13, 2025
e751e65
Add missing imports
kean Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 21 additions & 10 deletions Modules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ let package = Package(
.iOS(.v16),
],
products: XcodeSupport.products + [
.library(name: "JetpackStatsWidgetsCore", targets: ["JetpackStatsWidgetsCore"]),
.library(name: "AsyncImageKit", targets: ["AsyncImageKit"]),
.library(name: "DesignSystem", targets: ["DesignSystem"]),
.library(name: "JetpackStatsWidgetsCore", targets: ["JetpackStatsWidgetsCore"]),
.library(name: "WordPressFlux", targets: ["WordPressFlux"]),
.library(name: "WordPressMedia", targets: ["WordPressMedia"]),
.library(name: "WordPressShared", targets: ["WordPressShared"]),
.library(name: "WordPressUI", targets: ["WordPressUI"]),
],
dependencies: [
.package(url: "https://github.com/airbnb/lottie-ios", from: "4.4.0"),
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.9.1"),
.package(url: "https://github.com/Alamofire/AlamofireImage", from: "4.3.0"),
.package(url: "https://github.com/AliSoftware/OHHTTPStubs", from: "9.1.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
.package(url: "https://github.com/Automattic/Automattic-Tracks-iOS", from: "3.4.2"),
.package(url: "https://github.com/Automattic/AutomatticAbout-swift", from: "1.1.4"),
.package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", from: "3.1.1"),
Expand Down Expand Up @@ -52,23 +52,34 @@ let package = Package(
.package(url: "https://github.com/Automattic/color-studio", branch: "trunk"),
],
targets: XcodeSupport.targets + [
.target(name: "JetpackStatsWidgetsCore", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "AsyncImageKit", dependencies: [
.product(name: "Collections", package: "swift-collections"),
.product(name: "Gifu", package: "Gifu"),
]),
.target(name: "DesignSystem", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "JetpackStatsWidgetsCore", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "UITestsFoundation", dependencies: [
.product(name: "ScreenObject", package: "ScreenObject"),
.product(name: "XCUITestHelpers", package: "XCUITestHelpers"),
], swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressFlux", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressMedia"),
.target(name: "WordPressSharedObjC", resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressShared", dependencies: [.target(name: "WordPressSharedObjC")], resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressTesting", resources: [.process("Resources")]),
.target(name: "WordPressUI", dependencies: [.target(name: "WordPressShared")], resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)]),
.target(
name: "WordPressUI",
dependencies: [
"AsyncImageKit",
.target(name: "WordPressShared")
],
resources: [.process("Resources")],
swiftSettings: [.swiftLanguageMode(.v5)]
),
.testTarget(name: "JetpackStatsWidgetsCoreTests", dependencies: [.target(name: "JetpackStatsWidgetsCore")], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(name: "DesignSystemTests", dependencies: [.target(name: "DesignSystem")], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(name: "WordPressFluxTests", dependencies: ["WordPressFlux"], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(name: "WordPressMediaTests", dependencies: [
.target(name: "WordPressMedia"),
.testTarget(name: "AsyncImageKitTests", dependencies: [
.target(name: "AsyncImageKit"),
.target(name: "WordPressTesting"),
.product(name: "OHHTTPStubsSwift", package: "OHHTTPStubs")
]),
Expand Down Expand Up @@ -143,10 +154,9 @@ enum XcodeSupport {
"JetpackStatsWidgetsCore",
"WordPressFlux",
"WordPressShared",
"WordPressMedia",
"AsyncImageKit",
"WordPressUI",
.product(name: "Alamofire", package: "Alamofire"),
.product(name: "AlamofireImage", package: "AlamofireImage"),
.product(name: "AutomatticAbout", package: "AutomatticAbout-swift"),
.product(name: "AutomatticTracks", package: "Automattic-Tracks-iOS"),
.product(name: "CocoaLumberjack", package: "CocoaLumberjack"),
Expand Down Expand Up @@ -191,6 +201,7 @@ enum XcodeSupport {
.xcodeTarget("XcodeTarget_StatsWidget", dependencies: [
"JetpackStatsWidgetsCore",
"WordPressShared",
"WordPressUI",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
.product(name: "WordPressAPI", package: "wordpress-rs"),
.product(name: "ColorStudio", package: "color-studio"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private extension Data {
}
}

private extension CGSize {
extension CGSize {
func scaled(by scale: CGFloat) -> CGSize {
CGSize(width: width * scale, height: height * scale)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import UIKit
/// The system that downloads and caches images, and prepares them for display.
@ImageDownloaderActor
public final class ImageDownloader {
public nonisolated static let shared = ImageDownloader()

private nonisolated let cache: MemoryCacheProtocol
private let authenticator: MediaRequestAuthenticatorProtocol?

private let urlSession = URLSession {
$0.urlCache = nil
Expand All @@ -21,14 +22,12 @@ public final class ImageDownloader {
private var tasks: [String: ImageDataTask] = [:]

public nonisolated init(
cache: MemoryCacheProtocol = MemoryCache.shared,
authenticator: MediaRequestAuthenticatorProtocol?
cache: MemoryCacheProtocol = MemoryCache.shared
) {
self.cache = cache
self.authenticator = authenticator
}

public func image(from url: URL, host: MediaHost? = nil, options: ImageRequestOptions = .init()) async throws -> UIImage {
public func image(from url: URL, host: MediaHostProtocol? = nil, options: ImageRequestOptions = .init()) async throws -> UIImage {
try await image(for: ImageRequest(url: url, host: host, options: options))
}

Expand All @@ -39,7 +38,7 @@ public final class ImageDownloader {
return image
}
let data = try await data(for: request)
let image = try await ImageDecoder.makeImage(from: data, size: options.size)
let image = try await ImageDecoder.makeImage(from: data, size: options.size.map(CGSize.init))
if options.isMemoryCacheEnabled {
cache[key] = image
}
Expand All @@ -55,8 +54,8 @@ public final class ImageDownloader {
switch request.source {
case .url(let url, let host):
var request: URLRequest
if let host, let authenticator {
request = try await authenticator.authenticatedRequest(for: url, host: host)
if let host {
request = try await host.authenticatedRequest(for: url)
} else {
request = URLRequest(url: url)
}
Expand All @@ -69,24 +68,30 @@ public final class ImageDownloader {

// MARK: - Caching

/// Returns an image from the memory cache.
nonisolated public func cachedImage(for request: ImageRequest) -> UIImage? {
guard let imageURL = request.source.url else { return nil }
return cachedImage(for: imageURL, size: request.options.size)
}

/// Returns an image from the memory cache.
///
/// - note: Use it to retrieve the image synchronously, which is no not possible
/// with the async functions.
nonisolated public func cachedImage(for imageURL: URL, size: CGSize? = nil) -> UIImage? {
nonisolated public func cachedImage(for imageURL: URL, size: ImageSize? = nil) -> UIImage? {
cache[makeKey(for: imageURL, size: size)]
}

nonisolated public func setCachedImage(_ image: UIImage?, for imageURL: URL, size: CGSize? = nil) {
nonisolated public func setCachedImage(_ image: UIImage?, for imageURL: URL, size: ImageSize? = nil) {
cache[makeKey(for: imageURL, size: size)] = image
}

private nonisolated func makeKey(for imageURL: URL?, size: CGSize?) -> String {
private nonisolated func makeKey(for imageURL: URL?, size: ImageSize?) -> String {
guard let imageURL else {
assertionFailure("The request.url was nil") // This should never happen
return ""
}
return imageURL.absoluteString + (size.map { "?size=\($0)" } ?? "")
return imageURL.absoluteString + (size.map { "?w=\($0.width),h=\($0.height)" } ?? "")
}

public func clearURLSessionCache() {
Expand Down Expand Up @@ -189,6 +194,6 @@ private extension URLSession {
}
}

public protocol MediaRequestAuthenticatorProtocol: Sendable {
@MainActor func authenticatedRequest(for url: URL, host: MediaHost) async throws -> URLRequest
public protocol MediaHostProtocol: Sendable {
@MainActor func authenticatedRequest(for url: URL) async throws -> URLRequest
}
111 changes: 111 additions & 0 deletions Modules/Sources/AsyncImageKit/ImagePrefetcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import UIKit
import Collections

@ImageDownloaderActor
public final class ImagePrefetcher {
private let downloader: ImageDownloader
private let maxConcurrentTasks: Int
private var queue = OrderedDictionary<PrefetchKey, PrefetchTask>()
private var numberOfActiveTasks = 0

deinit {
let tasks = queue.values.compactMap(\.task)
for task in tasks {
task.cancel()
}
}

public nonisolated init(
downloader: ImageDownloader = .shared,
maxConcurrentTasks: Int = 2
) {
self.downloader = downloader
self.maxConcurrentTasks = maxConcurrentTasks
}

public nonisolated func startPrefetching(for requests: [ImageRequest]) {
Task { @ImageDownloaderActor in
for request in requests {
startPrefetching(for: request)
}
performPendingTasks()
}
}

private func startPrefetching(for request: ImageRequest) {
let key = PrefetchKey(request: request)
guard queue[key] == nil else {
return
}
queue[key] = PrefetchTask()
}

private func performPendingTasks() {
var index = 0
func nextPendingTask() -> (PrefetchKey, PrefetchTask)? {
while index < queue.count {
if queue.elements[index].value.task == nil {
return queue.elements[index]
}
index += 1
}
return nil
}
while numberOfActiveTasks < maxConcurrentTasks, let (key, task) = nextPendingTask() {
task.task = Task {
await self.actuallyPrefetchImage(for: key.request)
}
numberOfActiveTasks += 1
}
}

private func actuallyPrefetchImage(for request: ImageRequest) async {
_ = try? await downloader.image(for: request)

numberOfActiveTasks -= 1
queue[PrefetchKey(request: request)] = nil
performPendingTasks()
}

public nonisolated func stopPrefetching(for requests: [ImageRequest]) {
Task { @ImageDownloaderActor in
for request in requests {
stopPrefetching(for: request)
}
performPendingTasks()
}
}

private func stopPrefetching(for request: ImageRequest) {
let key = PrefetchKey(request: request)
if let task = queue.removeValue(forKey: key) {
task.task?.cancel()
}
}

public nonisolated func stopAll() {
Task { @ImageDownloaderActor in
for (_, value) in queue {
value.task?.cancel()
}
queue.removeAll()
}
}

private struct PrefetchKey: Hashable, Sendable {
let request: ImageRequest

func hash(into hasher: inout Hasher) {
request.source.url?.hash(into: &hasher)
}

static func == (lhs: PrefetchKey, rhs: PrefetchKey) -> Bool {
let (lhs, rhs) = (lhs.request, rhs.request)
return (lhs.source.url, lhs.options) == (rhs.source.url, rhs.options)
}
}

private final class PrefetchTask: @unchecked Sendable {
var task: Task<Void, Error>?
}
}
84 changes: 84 additions & 0 deletions Modules/Sources/AsyncImageKit/ImageRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import UIKit

public final class ImageRequest: Sendable {
public enum Source: Sendable {
case url(URL, MediaHostProtocol?)
case urlRequest(URLRequest)

var url: URL? {
switch self {
case .url(let url, _): url
case .urlRequest(let request): request.url
}
}
}

let source: Source
let options: ImageRequestOptions

public init(url: URL, host: MediaHostProtocol? = nil, options: ImageRequestOptions = .init()) {
self.source = .url(url, host)
self.options = options
}

public init(urlRequest: URLRequest, options: ImageRequestOptions = .init()) {
self.source = .urlRequest(urlRequest)
self.options = options
}
}

public struct ImageRequestOptions: Hashable, Sendable {
/// Resize the thumbnail to the given size. By default, `nil`.
public var size: ImageSize?

/// If enabled, uses ``MemoryCache`` for caching decompressed images.
public var isMemoryCacheEnabled = true

/// If enabled, uses `URLSession` preconfigured with a custom `URLCache`
/// with a relatively high disk capacity. By default, `true`.
public var isDiskCacheEnabled = true

public init(
size: ImageSize? = nil,
isMemoryCacheEnabled: Bool = true,
isDiskCacheEnabled: Bool = true
) {
self.size = size
self.isMemoryCacheEnabled = isMemoryCacheEnabled
self.isDiskCacheEnabled = isDiskCacheEnabled
}
}

/// Image size in **pixels**.
public struct ImageSize: Hashable, Sendable {
public let width: CGFloat
public let height: CGFloat

public init(width: CGFloat, height: CGFloat) {
self.width = width
self.height = height
}

public init(_ size: CGSize) {
self.width = size.width
self.height = size.height
}

/// Initializes `ImageSize` with the given size scaled for the given view.
@MainActor
public init(scaling size: CGSize, in view: UIView) {
self.init(size.scaled(by: view.traitCollection.displayScale))
}

/// Initializes `ImageSize` with the given size scaled for the current trait
/// collection display scale.
public init(scaling size: CGSize) {
self.init(size.scaled(by: UITraitCollection.current.displayScale))
}
}

extension CGSize {
init(_ size: ImageSize) {
self.init(width: size.width, height: size.height)
}
}
Loading