Improve camera UX: Direct camera access with camera icon

- Change paperclip icon to camera icon for clearer affordance
- Open camera directly on tap (no confirmation dialog)
- Add CameraPickerView wrapper for UIImagePickerController
- Remove PhotosPicker in favor of direct camera access
- Images still processed through ImageUtils with EXIF stripping
- Accessibility: Added 'Take photo' label
This commit is contained in:
jack
2025-10-18 13:45:04 +02:00
parent 9d738cff45
commit a84b8c05f1
2 changed files with 24136 additions and 23968 deletions
+539 -392
View File
@@ -182,8 +182,188 @@
}, },
"shouldTranslate" : false "shouldTranslate" : false
}, },
"%@ active" : {
"comment" : "A label at the bottom of the people list sheet showing the number of active users.",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ نشطين"
}
},
"bn" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ সক্রিয়"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktiv"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ active"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ activos"
}
},
"fil" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktibo"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ actifs"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ פעילים"
}
},
"hi" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ सक्रिय"
}
},
"id" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktif"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ attivi"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ 人がアクティブ"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "활성 사용자 %@명"
}
},
"ms" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktif"
}
},
"ne" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ सक्रिय"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ actief"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktywni"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ ativos"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ ativos"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ активных"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktiva"
}
},
"ta" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ செயலில்"
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ กำลังใช้งาน"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ aktif"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ активних"
}
},
"ur" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ فعال"
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ đang hoạt động"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "活跃用户 %@ 名"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "活躍使用者 %@ 位"
}
}
}
},
"%@/%@" : { "%@/%@" : {
"comment" : "A label showing the number of messages that have been successfully delivered, followed by a slash and the total number of messages. Both numbers are displayed in monospaced font.", "comment" : "A label showing the number of messages that have been successfully delivered, followed by a slash and the total number of messages. Both numbers are displayed in monospaced font.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"ar" : { "ar" : {
@@ -362,40 +542,6 @@
} }
} }
}, },
"%@ active": {
"comment": "A label at the bottom of the people list sheet showing the number of active users.",
"localizations": {
"ar": { "stringUnit": { "state": "translated", "value": "%@ نشطين" } },
"bn": { "stringUnit": { "state": "translated", "value": "%@ সক্রিয়" } },
"de": { "stringUnit": { "state": "translated", "value": "%@ aktiv" } },
"en": { "stringUnit": { "state": "translated", "value": "%@ active" } },
"es": { "stringUnit": { "state": "translated", "value": "%@ activos" } },
"fil": { "stringUnit": { "state": "translated", "value": "%@ aktibo" } },
"fr": { "stringUnit": { "state": "translated", "value": "%@ actifs" } },
"he": { "stringUnit": { "state": "translated", "value": "%@ פעילים" } },
"hi": { "stringUnit": { "state": "translated", "value": "%@ सक्रिय" } },
"id": { "stringUnit": { "state": "translated", "value": "%@ aktif" } },
"it": { "stringUnit": { "state": "translated", "value": "%@ attivi" } },
"ja": { "stringUnit": { "state": "translated", "value": "%@ 人がアクティブ" } },
"ko": { "stringUnit": { "state": "translated", "value": "활성 사용자 %@명" } },
"ms": { "stringUnit": { "state": "translated", "value": "%@ aktif" } },
"ne": { "stringUnit": { "state": "translated", "value": "%@ सक्रिय" } },
"nl": { "stringUnit": { "state": "translated", "value": "%@ actief" } },
"pl": { "stringUnit": { "state": "translated", "value": "%@ aktywni" } },
"pt": { "stringUnit": { "state": "translated", "value": "%@ ativos" } },
"pt-BR": { "stringUnit": { "state": "translated", "value": "%@ ativos" } },
"ru": { "stringUnit": { "state": "translated", "value": "%@ активных" } },
"sv": { "stringUnit": { "state": "translated", "value": "%@ aktiva" } },
"ta": { "stringUnit": { "state": "translated", "value": "%@ செயலில்" } },
"th": { "stringUnit": { "state": "translated", "value": "%@ กำลังใช้งาน" } },
"tr": { "stringUnit": { "state": "translated", "value": "%@ aktif" } },
"uk": { "stringUnit": { "state": "translated", "value": "%@ активних" } },
"ur": { "stringUnit": { "state": "translated", "value": "%@ فعال" } },
"vi": { "stringUnit": { "state": "translated", "value": "%@ đang hoạt động" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "活跃用户 %@ 名" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "活躍使用者 %@ 位" } }
}
},
"app_info.app_name" : { "app_info.app_name" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {
@@ -30905,6 +31051,365 @@
} }
} }
}, },
"open" : {
"comment" : "Button to open attached file",
"extractionState" : "stale",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "فتح"
}
},
"bn" : {
"stringUnit" : {
"state" : "translated",
"value" : "খুলুন"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "öffnen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "open"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "abrir"
}
},
"fil" : {
"stringUnit" : {
"state" : "translated",
"value" : "buksan"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "ouvrir"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "פתח"
}
},
"hi" : {
"stringUnit" : {
"state" : "translated",
"value" : "खोलें"
}
},
"id" : {
"stringUnit" : {
"state" : "translated",
"value" : "buka"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "apri"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "開く"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "열기"
}
},
"ms" : {
"stringUnit" : {
"state" : "translated",
"value" : "buka"
}
},
"ne" : {
"stringUnit" : {
"state" : "translated",
"value" : "खोल्नुहोस्"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "openen"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "otwórz"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "abrir"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "abrir"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "открыть"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "öppna"
}
},
"ta" : {
"stringUnit" : {
"state" : "translated",
"value" : "திற"
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "เปิด"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "aç"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "відкрити"
}
},
"ur" : {
"stringUnit" : {
"state" : "translated",
"value" : "کھولیں"
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "mở"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "打开"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "開啟"
}
}
}
},
"recording %@" : {
"comment" : "Voice note recording duration indicator",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "التسجيل %@"
}
},
"bn" : {
"stringUnit" : {
"state" : "translated",
"value" : "রেকর্ডিং %@"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "aufnahme %@"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "recording %@"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "grabando %@"
}
},
"fil" : {
"stringUnit" : {
"state" : "translated",
"value" : "nagrerekord %@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "enregistrement %@"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "מקליט %@"
}
},
"hi" : {
"stringUnit" : {
"state" : "translated",
"value" : "रिकॉर्डिंग %@"
}
},
"id" : {
"stringUnit" : {
"state" : "translated",
"value" : "merekam %@"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "registrazione %@"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "録音中 %@"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "녹음 중 %@"
}
},
"ms" : {
"stringUnit" : {
"state" : "translated",
"value" : "merakam %@"
}
},
"ne" : {
"stringUnit" : {
"state" : "translated",
"value" : "रेकर्डिङ %@"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "opname %@"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "nagrywanie %@"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "gravando %@"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "gravando %@"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "запись %@"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "spelar in %@"
}
},
"ta" : {
"stringUnit" : {
"state" : "translated",
"value" : "பதிவு செய்கிறது %@"
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "กำลังบันทึก %@"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "kaydediliyor %@"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "запис %@"
}
},
"ur" : {
"stringUnit" : {
"state" : "translated",
"value" : "ریکارڈنگ %@"
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "đang ghi %@"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "录音中 %@"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "錄音中 %@"
}
}
}
},
"save" : { "save" : {
"comment" : "Button to save media to device", "comment" : "Button to save media to device",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -35559,364 +36064,6 @@
} }
} }
} }
},
"open": {
"comment": "Button to open attached file",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "فتح"
}
},
"bn": {
"stringUnit": {
"state": "translated",
"value": "খুলুন"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "öffnen"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "open"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "abrir"
}
},
"fil": {
"stringUnit": {
"state": "translated",
"value": "buksan"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "ouvrir"
}
},
"he": {
"stringUnit": {
"state": "translated",
"value": "פתח"
}
},
"hi": {
"stringUnit": {
"state": "translated",
"value": "खोलें"
}
},
"id": {
"stringUnit": {
"state": "translated",
"value": "buka"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "apri"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "開く"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "열기"
}
},
"ms": {
"stringUnit": {
"state": "translated",
"value": "buka"
}
},
"ne": {
"stringUnit": {
"state": "translated",
"value": "खोल्नुहोस्"
}
},
"nl": {
"stringUnit": {
"state": "translated",
"value": "openen"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "otwórz"
}
},
"pt": {
"stringUnit": {
"state": "translated",
"value": "abrir"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "abrir"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "открыть"
}
},
"sv": {
"stringUnit": {
"state": "translated",
"value": "öppna"
}
},
"ta": {
"stringUnit": {
"state": "translated",
"value": "திற"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "เปิด"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "aç"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "відкрити"
}
},
"ur": {
"stringUnit": {
"state": "translated",
"value": "کھولیں"
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "mở"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "打开"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "開啟"
}
}
}
},
"recording %@": {
"comment": "Voice note recording duration indicator",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "التسجيل %@"
}
},
"bn": {
"stringUnit": {
"state": "translated",
"value": "রেকর্ডিং %@"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "aufnahme %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "recording %@"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "grabando %@"
}
},
"fil": {
"stringUnit": {
"state": "translated",
"value": "nagrerekord %@"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "enregistrement %@"
}
},
"he": {
"stringUnit": {
"state": "translated",
"value": "מקליט %@"
}
},
"hi": {
"stringUnit": {
"state": "translated",
"value": "रिकॉर्डिंग %@"
}
},
"id": {
"stringUnit": {
"state": "translated",
"value": "merekam %@"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "registrazione %@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "録音中 %@"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "녹음 중 %@"
}
},
"ms": {
"stringUnit": {
"state": "translated",
"value": "merakam %@"
}
},
"ne": {
"stringUnit": {
"state": "translated",
"value": "रेकर्डिङ %@"
}
},
"nl": {
"stringUnit": {
"state": "translated",
"value": "opname %@"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "nagrywanie %@"
}
},
"pt": {
"stringUnit": {
"state": "translated",
"value": "gravando %@"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "gravando %@"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "запись %@"
}
},
"sv": {
"stringUnit": {
"state": "translated",
"value": "spelar in %@"
}
},
"ta": {
"stringUnit": {
"state": "translated",
"value": "பதிவு செய்கிறது %@"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "กำลังบันทึก %@"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "kaydediliyor %@"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "запис %@"
}
},
"ur": {
"stringUnit": {
"state": "translated",
"value": "ریکارڈنگ %@"
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "đang ghi %@"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "录音中 %@"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "錄音中 %@"
}
}
}
} }
}, },
"version" : "1.1" "version" : "1.1"
+62 -41
View File
@@ -15,9 +15,6 @@ import AppKit
#endif #endif
import UniformTypeIdentifiers import UniformTypeIdentifiers
import BitLogger import BitLogger
#if canImport(PhotosUI)
import PhotosUI
#endif
// MARK: - Supporting Types // MARK: - Supporting Types
@@ -72,10 +69,8 @@ struct ContentView: View {
@State private var recordingTimer: Timer? @State private var recordingTimer: Timer?
@State private var recordingStartDate: Date? @State private var recordingStartDate: Date?
#if os(iOS) #if os(iOS)
@State private var showPhotoPicker = false @State private var showCameraPicker = false
@State private var selectedPhotoPickerItem: PhotosPickerItem?
#endif #endif
@State private var showAttachmentActions = false
@ScaledMetric(relativeTo: .body) private var headerHeight: CGFloat = 44 @ScaledMetric(relativeTo: .body) private var headerHeight: CGFloat = 44
@ScaledMetric(relativeTo: .subheadline) private var headerPeerIconSize: CGFloat = 11 @ScaledMetric(relativeTo: .subheadline) private var headerPeerIconSize: CGFloat = 11
@ScaledMetric(relativeTo: .subheadline) private var headerPeerCountFontSize: CGFloat = 12 @ScaledMetric(relativeTo: .subheadline) private var headerPeerCountFontSize: CGFloat = 12
@@ -209,10 +204,22 @@ struct ContentView: View {
} }
} }
#if os(iOS) #if os(iOS)
.photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotoPickerItem, matching: .images) .sheet(isPresented: $showCameraPicker) {
.onChange(of: selectedPhotoPickerItem) { newItem in CameraPickerView { image in
guard let item = newItem else { return } showCameraPicker = false
Task { await handlePhotoSelection(item) } if let image = image {
Task {
do {
let processedURL = try ImageUtils.processImage(image)
await MainActor.run {
viewModel.sendImage(from: processedURL)
}
} catch {
SecureLogger.error("Camera image processing failed: \(error)", category: .session)
}
}
}
}
} }
#endif #endif
.sheet(isPresented: Binding( .sheet(isPresented: Binding(
@@ -223,15 +230,6 @@ struct ContentView: View {
ImagePreviewView(url: url) ImagePreviewView(url: url)
} }
} }
.confirmationDialog("Attach", isPresented: $showAttachmentActions, titleVisibility: .visible) {
#if os(iOS)
Button("Image") {
showAttachmentActions = false
DispatchQueue.main.async { showPhotoPicker = true }
}
#endif
Button("Cancel", role: .cancel) {}
}
.alert("Recording Error", isPresented: $showRecordingAlert, actions: { .alert("Recording Error", isPresented: $showRecordingAlert, actions: {
Button("OK", role: .cancel) {} Button("OK", role: .cancel) {}
}, message: { }, message: {
@@ -1805,13 +1803,18 @@ private extension ContentView {
} }
var attachmentButton: some View { var attachmentButton: some View {
Button(action: { showAttachmentActions = true }) { Button(action: {
Image(systemName: "paperclip.circle.fill") #if os(iOS)
showCameraPicker = true
#endif
}) {
Image(systemName: "camera.circle.fill")
.font(.bitchatSystem(size: 24)) .font(.bitchatSystem(size: 24))
.foregroundColor(composerAccentColor) .foregroundColor(composerAccentColor)
.frame(width: 36, height: 36) .frame(width: 36, height: 36)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel("Take photo")
} }
var sendOrMicButton: some View { var sendOrMicButton: some View {
@@ -1980,26 +1983,6 @@ private extension ContentView {
} }
} }
#if os(iOS)
func handlePhotoSelection(_ item: PhotosPickerItem) async {
defer { Task { @MainActor in selectedPhotoPickerItem = nil } }
do {
if let data = try await item.loadTransferable(type: Data.self) {
let tempURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("jpg")
try data.write(to: tempURL, options: Data.WritingOptions.atomic)
await MainActor.run {
viewModel.sendImage(from: tempURL) {
try? FileManager.default.removeItem(at: tempURL)
}
}
}
} catch {
SecureLogger.error("Photo picker load failed: \(error)", category: .session)
}
}
#endif
func applicationFilesDirectory() -> URL? { func applicationFilesDirectory() -> URL? {
// Cache the directory lookup to avoid repeated FileManager calls during view rendering // Cache the directory lookup to avoid repeated FileManager calls during view rendering
@@ -2143,3 +2126,41 @@ struct ImagePreviewView: View {
} }
#endif #endif
} }
#if os(iOS)
// MARK: - Camera Picker
struct CameraPickerView: UIViewControllerRepresentable {
let completion: (UIImage?) -> Void
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = context.coordinator
picker.allowsEditing = false
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(completion: completion)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let completion: (UIImage?) -> Void
init(completion: @escaping (UIImage?) -> Void) {
self.completion = completion
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let image = info[.originalImage] as? UIImage
completion(image)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
completion(nil)
}
}
}
#endif