Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions apps/ios/Sources/Litter/Models/ConversationAttachmentSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,42 @@ enum ConversationAttachmentSupport {
return inputs
}

private static let maxTransportPixelDimension: CGFloat = 2048
private static let minTransportPixelDimension: CGFloat = 1024
private static let maxTransportImageBytes = 1_200_000
private static let transportJpegQuality: CGFloat = 0.7

private static func encodedImageData(for image: UIImage) -> (data: Data, mimeType: String)? {
if image.litterHasAlpha, let pngData = image.pngData() {
var current = downscaled(image, longestSide: maxTransportPixelDimension)
if current.litterHasAlpha,
let pngData = current.pngData(),
pngData.count <= maxTransportImageBytes {
return (pngData, "image/png")
}
if let jpegData = image.jpegData(compressionQuality: 0.85) {
return (jpegData, "image/jpeg")
var dimension = maxTransportPixelDimension
while true {
guard let jpegData = current.jpegData(compressionQuality: transportJpegQuality) else { break }
if jpegData.count <= maxTransportImageBytes || dimension <= minTransportPixelDimension {
return (jpegData, "image/jpeg")
}
dimension = max(dimension * 0.75, minTransportPixelDimension)
current = downscaled(current, longestSide: dimension)
}
if let pngData = image.pngData() {
return (pngData, "image/png")
return current.pngData().map { ($0, "image/png") }
}

private static func downscaled(_ image: UIImage, longestSide limit: CGFloat) -> UIImage {
let pixelWidth = image.size.width * image.scale
let pixelHeight = image.size.height * image.scale
let longestSide = max(pixelWidth, pixelHeight)
guard longestSide > limit else { return image }
let ratio = limit / longestSide
let targetSize = CGSize(width: pixelWidth * ratio, height: pixelHeight * ratio)
let format = UIGraphicsImageRendererFormat()
format.scale = 1
return UIGraphicsImageRenderer(size: targetSize, format: format).image { _ in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
return nil
}

private static func isSupportedImageFile(_ url: URL) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import UIKit
/// in-conversation composer and the photo pickers cannot drift apart.
enum ComposerAttachmentLimits {
/// Maximum number of images that can ride along with a single turn.
static let maxImages = 4
static let maxImages = 10
}

struct ConversationComposerContentView: View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ struct ConversationComposerModalCoordinator<Content: View>: View {
@State private var modelSelectorDetent: PresentationDetent = .large

/// Bridge binding for `CameraView` which expects a single `UIImage?`.
/// Appends the captured photo to the `attachedImages` array (capped at 4)
/// instead of replacing existing attachments.
/// Appends the captured photo to the `attachedImages` array (capped at
/// the shared limit) instead of replacing existing attachments.
private var cameraImageBinding: Binding<UIImage?> {
Binding(
get: { attachedImages.last },
set: { newImage in
guard let newImage else { return }
if attachedImages.count < 4 {
if attachedImages.count < ComposerAttachmentLimits.maxImages {
attachedImages.append(newImage)
}
}
Expand Down Expand Up @@ -182,7 +182,7 @@ struct ConversationComposerModalCoordinator<Content: View>: View {
.presentationDetents([.height(attachSheetDetentHeight)])
.presentationDragIndicator(.visible)
}
.photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotos, maxSelectionCount: 4, matching: .images)
.photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotos, maxSelectionCount: ComposerAttachmentLimits.maxImages, matching: .images)
.fileImporter(
isPresented: $showFileImporter,
allowedContentTypes: ConversationAttachmentSupport.supportedFileContentTypes,
Expand Down