-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Support Ticket Attachments #24972
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
base: trunk
Are you sure you want to change the base?
Changes from all commits
3b45000
d8b9922
b06e935
32e7c91
56608ab
34c0f56
e4e8709
3c74c74
cf686ac
9179bec
2ce2e73
17b5120
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import UIKit | ||
| import Foundation | ||
|
|
||
| /// Fetches URLs for favicons for sites. | ||
| public actor FaviconService { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import UIKit | ||
| import Foundation | ||
| import Collections | ||
|
|
||
| @ImageDownloaderActor | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import SwiftUI | ||
| import AVFoundation | ||
|
|
||
| /// Asynchronous Image View that replicates the public API of `SwiftUI.AsyncImage` to fetch | ||
| /// a video preview thumbnail. | ||
| /// It uses `ImageDownloader` to fetch and cache the images. | ||
| public struct CachedAsyncVideoPreview<Content>: View where Content: View { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like a copy of I'd suggest adding an |
||
| @State private var phase: AsyncImagePhase = .empty | ||
| private let url: URL? | ||
| private let content: (AsyncImagePhase) -> Content | ||
| private let imageDownloader: ImageDownloader | ||
| private let host: MediaHostProtocol? | ||
|
|
||
| public var body: some View { | ||
| content(phase) | ||
| .task(id: url) { await fetchImage() } | ||
| } | ||
|
|
||
| // MARK: - Initializers | ||
|
|
||
| /// Initializes an image without any customization. | ||
| /// Provides a plain color as placeholder | ||
| public init(url: URL?) where Content == _ConditionalContent<Image, Color> { | ||
| self.init(url: url) { phase in | ||
| if let image = phase.image { | ||
| image | ||
| } else { | ||
| Color(uiColor: .secondarySystemBackground) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Allows content customization and providing a placeholder that will be shown | ||
| /// until the image download is finalized. | ||
| public init<I, P>( | ||
| url: URL?, | ||
| host: MediaHostProtocol? = nil, | ||
| @ViewBuilder content: @escaping (Image) -> I, | ||
| @ViewBuilder placeholder: @escaping () -> P | ||
| ) where Content == _ConditionalContent<I, P>, I: View, P: View { | ||
| self.init(url: url, host: host) { phase in | ||
| if let image = phase.image { | ||
| content(image) | ||
| } else { | ||
| placeholder() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public init( | ||
| url: URL?, | ||
| host: MediaHostProtocol? = nil, | ||
| imageDownloader: ImageDownloader = .shared, | ||
| @ViewBuilder content: @escaping (AsyncImagePhase) -> Content | ||
| ) { | ||
| self.url = url | ||
| self.host = host | ||
| self.imageDownloader = imageDownloader | ||
| self.content = content | ||
| } | ||
|
|
||
| // MARK: - Helpers | ||
|
|
||
| private func fetchImage() async { | ||
| do { | ||
| guard let url else { | ||
| phase = .empty | ||
| return | ||
| } | ||
|
|
||
| if let image = imageDownloader.cachedImage(for: url) { | ||
| phase = .success(Image(uiImage: image)) | ||
| } else { | ||
| let image = try await imageDownloader.image(for: ImageRequest(videoUrl: url)) | ||
| phase = .success(Image(uiImage: image)) | ||
| } | ||
| } catch { | ||
| phase = .failure(error) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #Preview { | ||
| let url = URL(string: "https://a8c.zendesk.com/attachments/token/Le9xjU6B0nfYjtActesrzRrcm/?name=file_example_MP4_1920_18MG.mp4")! | ||
|
|
||
| CachedAsyncVideoPreview(url: url) { image in | ||
| image.resizable().scaledToFit() | ||
| } placeholder: { | ||
| ProgressView() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,4 +93,20 @@ extension Task where Failure == Error { | |
| return try await MainActor.run(body: operation) | ||
| } | ||
| } | ||
|
|
||
| enum RunForAtLeastResult<T>: Sendable where T: Sendable { | ||
| case result(T) | ||
| case wait | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This enum is not used. |
||
|
|
||
| static func runForAtLeast<C>( | ||
| _ duration: C.Instant.Duration, | ||
| operation: @escaping @Sendable () async throws -> Success, | ||
| clock: C = .continuous | ||
| ) async throws -> Success where C: Clock { | ||
| async let waitResult: () = try await clock.sleep(for: duration) | ||
| async let performTask = try await operation() | ||
|
|
||
| return try await (waitResult, performTask).1 | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's relatively expensive to convert it to data and back. Does it needs to support
data(for request). Would make sense to move it underimage(for request)? Alternately,data(for request)could callimage(for request)and then convert it to data so both would be supported.