react-native-attachment-kit 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -2
- package/android/CMakeLists.txt +3 -0
- package/android/build.gradle +11 -2
- package/android/src/main/libs/rnattachmentkit-release.aar +0 -0
- package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +48 -2
- package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +48 -2
- package/ios/RNAttachmentKit.swift +133 -4
- package/ios/RNAttachmentKit.xcframework/Info.plist +5 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.abi.json +727 -415
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.private.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeResources +12 -12
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeSignature +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json +727 -415
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +727 -415
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +12 -1
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeResources +20 -20
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/types.ts +34 -1
|
@@ -57,6 +57,8 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
57
57
|
/// 동영상 변환 진행 HUD 를 띄울지 — video.showProgressHUD, 없으면 asyncProcessing 이 아닐 때만
|
|
58
58
|
/// (앱이 onProcessingStart / onVideoProgress / onImageProcessed 로 말풍선에 진행 상황을 그리는 구조면 HUD 없이)
|
|
59
59
|
private var galleryShowsVideoHUD = true
|
|
60
|
+
/// 사진 썸네일 (GalleryOptions.thumbnail). nil 이면 만들지 않음 (기존과 같음)
|
|
61
|
+
private var galleryThumbnail: PhotoThumbnailOptions?
|
|
60
62
|
|
|
61
63
|
// MARK: - Module Setup
|
|
62
64
|
|
|
@@ -240,6 +242,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
240
242
|
func openMapViewer(_ options: NSDictionary,
|
|
241
243
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
242
244
|
reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
245
|
+
guard LicenseGate.isLicensed else {
|
|
246
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
247
|
+
}
|
|
243
248
|
guard let lat = RNAttachmentKit.cgFloat(options["latitude"]),
|
|
244
249
|
let lng = RNAttachmentKit.cgFloat(options["longitude"]) else {
|
|
245
250
|
reject("ERROR", "latitude and longitude are required", nil)
|
|
@@ -308,6 +313,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
308
313
|
func renderLocationThumbnail(_ options: NSDictionary,
|
|
309
314
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
310
315
|
reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
316
|
+
guard LicenseGate.isLicensed else {
|
|
317
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
318
|
+
}
|
|
311
319
|
guard let lat = RNAttachmentKit.cgFloat(options["latitude"]),
|
|
312
320
|
let lng = RNAttachmentKit.cgFloat(options["longitude"]),
|
|
313
321
|
let w = RNAttachmentKit.cgFloat(options["width"]), w > 0 else {
|
|
@@ -366,6 +374,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
366
374
|
func reverseGeocode(_ options: NSDictionary,
|
|
367
375
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
368
376
|
reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
377
|
+
guard LicenseGate.isLicensed else {
|
|
378
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
379
|
+
}
|
|
369
380
|
let lat = (options["latitude"] as? NSNumber)?.doubleValue ?? .nan
|
|
370
381
|
let lng = (options["longitude"] as? NSNumber)?.doubleValue ?? .nan
|
|
371
382
|
let lang = (options["languageCode"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? "en"
|
|
@@ -383,6 +394,10 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
383
394
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
384
395
|
reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
385
396
|
let dict = config as? [String: Any] ?? [:]
|
|
397
|
+
// 라이선스 키가 없어도 설정은 받아 둠 (AI 버튼은 보이고, 누르면 "라이선스 필요" 안내)
|
|
398
|
+
if !LicenseGate.isLicensed, (dict["enabled"] as? Bool) == true {
|
|
399
|
+
NSLog("[RNAttachmentKit] AI needs a license key — the AI buttons show a license notice until init() gets a valid key")
|
|
400
|
+
}
|
|
386
401
|
DispatchQueue.main.async {
|
|
387
402
|
AIBilling.apply(dict)
|
|
388
403
|
AIBilling.onUsage = { [weak self] body in self?.sendEvent(withName: "onAIUsage", body: body) }
|
|
@@ -421,6 +436,10 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
421
436
|
let cacheFiles: [String]? = (options["cacheFiles"] as? [Any])?.map { ($0 as? String) ?? "" }
|
|
422
437
|
// videos: images 와 같은 순서. 항목이 있으면 그 페이지는 동영상 (images[i] 는 포스터)
|
|
423
438
|
let viewerVideos: [ViewerVideoItem?] = (options["videos"] as? [Any])?.map { ViewerVideoItem(dictionary: $0) } ?? []
|
|
439
|
+
// 동영상 페이지는 라이선스 키가 있어야
|
|
440
|
+
if !LicenseGate.isLicensed, viewerVideos.contains(where: { $0 != nil }) {
|
|
441
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
442
|
+
}
|
|
424
443
|
let themeColorHex = options["themeColor"] as? String
|
|
425
444
|
let languageCode = options["languageCode"] as? String ?? "en"
|
|
426
445
|
let viewerTitle = options["title"] as? String
|
|
@@ -679,6 +698,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
679
698
|
var parsedMaxHeight: Int? = nil
|
|
680
699
|
if let maxW = options["maxWidth"] as? Int { parsedMaxWidth = maxW }
|
|
681
700
|
if let maxH = options["maxHeight"] as? Int { parsedMaxHeight = maxH }
|
|
701
|
+
let parsedThumbnail = PhotoThumbnailOptions(options["thumbnail"])
|
|
682
702
|
|
|
683
703
|
DispatchQueue.main.async {
|
|
684
704
|
guard let rootViewController = self.getRootViewController() else {
|
|
@@ -716,7 +736,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
716
736
|
|
|
717
737
|
editorVC.onConfirm = { [weak self, weak editorVC] (editedAsset, editedImage, filterState, caption) in
|
|
718
738
|
editorVC?.dismiss(animated: true) {
|
|
719
|
-
self?.processEditorResult(asset: editedAsset, croppedImage: editedImage, filterState: filterState, caption: caption, galleryVC: nil, targetMaxWidth: parsedMaxWidth, targetMaxHeight: parsedMaxHeight, resolve: { resultArr in
|
|
739
|
+
self?.processEditorResult(asset: editedAsset, croppedImage: editedImage, filterState: filterState, caption: caption, galleryVC: nil, targetMaxWidth: parsedMaxWidth, targetMaxHeight: parsedMaxHeight, thumbnail: parsedThumbnail, resolve: { resultArr in
|
|
720
740
|
if let arr = resultArr as? [[String: Any]], let first = arr.first {
|
|
721
741
|
resolve(first)
|
|
722
742
|
} else {
|
|
@@ -760,7 +780,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
760
780
|
|
|
761
781
|
editorVC.onConfirm = { [weak self, weak editorVC] (editedAsset, editedImage, filterState, caption) in
|
|
762
782
|
editorVC?.dismiss(animated: true) {
|
|
763
|
-
self?.processEditorResult(asset: editedAsset, croppedImage: editedImage, filterState: filterState, caption: caption, galleryVC: nil, targetMaxWidth: parsedMaxWidth, targetMaxHeight: parsedMaxHeight, resolve: { resultArr in
|
|
783
|
+
self?.processEditorResult(asset: editedAsset, croppedImage: editedImage, filterState: filterState, caption: caption, galleryVC: nil, targetMaxWidth: parsedMaxWidth, targetMaxHeight: parsedMaxHeight, thumbnail: parsedThumbnail, resolve: { resultArr in
|
|
764
784
|
var resultDict = (resultArr as? [[String: Any]])?.first ?? (resultArr as? [String: Any]) ?? [:]
|
|
765
785
|
// For remote URLs, we preserve originalUri if editedFileUri isn't provided
|
|
766
786
|
let originalRemoteUrl = (options["editedFileUri"] as? String) ?? uri
|
|
@@ -868,6 +888,8 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
868
888
|
// maxWidth, maxHeight 옵션 (미설정 또는 0이면 원본 크기 유지)
|
|
869
889
|
galleryVC.maxWidth = (options["maxWidth"] as? Int) ?? 0
|
|
870
890
|
galleryVC.maxHeight = (options["maxHeight"] as? Int) ?? 0
|
|
891
|
+
// 사진 썸네일 (동영상 포스터 규칙은 그대로 — video.posterFormat, 긴 변 512)
|
|
892
|
+
self.galleryThumbnail = PhotoThumbnailOptions(options["thumbnail"])
|
|
871
893
|
|
|
872
894
|
// outputFormat: "webp" | "jpg" | "png" (기본값: "webp")
|
|
873
895
|
let fmt = (options["outputFormat"] as? String)?.lowercased() ?? "webp"
|
|
@@ -973,6 +995,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
973
995
|
// 동영상: mediaType "photo"(기본) | "video" | "all" + video 옵션 (지시서 3-1)
|
|
974
996
|
let mediaTypeOption = (options["mediaType"] as? String)?.lowercased() ?? "photo"
|
|
975
997
|
galleryVC.mediaType = ["photo", "video", "all"].contains(mediaTypeOption) ? mediaTypeOption : "photo"
|
|
998
|
+
// 동영상은 라이선스 키가 있어야 — 없으면 갤러리가 사진만 보여 줌 (GalleryBaseViewController.viewDidLoad)
|
|
976
999
|
let videoOptions = VideoExportOptions(dictionary: options["video"] as? [String: Any])
|
|
977
1000
|
let asyncProcessing = options["asyncProcessing"] as? Bool ?? false
|
|
978
1001
|
self.galleryShowsVideoHUD = ((options["video"] as? [String: Any])?["showProgressHUD"] as? Bool) ?? !asyncProcessing
|
|
@@ -1004,6 +1027,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1004
1027
|
let galleryFormat = galleryVC.outputFormat // "webp" | "jpg" | "png"
|
|
1005
1028
|
let galleryAccent = galleryVC.parsedThemeColor
|
|
1006
1029
|
let galleryLanguage = galleryVC.languageCode
|
|
1030
|
+
let galleryThumb = self.galleryThumbnail
|
|
1007
1031
|
galleryVC.onImagesSelected = { [weak self, weak galleryVC] selectedImages in
|
|
1008
1032
|
guard let self = self else { resolve([]); return }
|
|
1009
1033
|
|
|
@@ -1176,6 +1200,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1176
1200
|
result["fileExtension"] = meta.fileExtension
|
|
1177
1201
|
result["fileSize"] = meta.fileSize
|
|
1178
1202
|
}
|
|
1203
|
+
// 썸네일: 같은 비트맵(방향·크롭·그리기 반영)에서
|
|
1204
|
+
result.merge(self.makePhotoThumbnail(targetImage, options: galleryThumb, index: index,
|
|
1205
|
+
timestamp: timestamp, tempDir: tempDir)) { $1 }
|
|
1179
1206
|
|
|
1180
1207
|
resultsLock.lock()
|
|
1181
1208
|
results.append(result)
|
|
@@ -1227,6 +1254,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1227
1254
|
result["fileExtension"] = meta.fileExtension
|
|
1228
1255
|
result["fileSize"] = meta.fileSize
|
|
1229
1256
|
}
|
|
1257
|
+
// 썸네일: 같은 비트맵(방향·크롭·그리기 반영)에서
|
|
1258
|
+
result.merge(self.makePhotoThumbnail(targetImage, options: galleryThumb, index: index,
|
|
1259
|
+
timestamp: timestamp, tempDir: tempDir)) { $1 }
|
|
1230
1260
|
|
|
1231
1261
|
resultsLock.lock()
|
|
1232
1262
|
results.append(result)
|
|
@@ -1621,6 +1651,66 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1621
1651
|
}
|
|
1622
1652
|
}
|
|
1623
1653
|
|
|
1654
|
+
// MARK: - Photo thumbnail (GalleryOptions.thumbnail / EditorOptions.thumbnail)
|
|
1655
|
+
|
|
1656
|
+
/// 결과 크기와 원본에서 잘라 쓸 영역 (픽셀). Android PhotoThumbnail.layout 과 같은 계산
|
|
1657
|
+
/// - fit: ratio = min(maxW/w, maxH/h, 1) → round(w·ratio) × round(h·ratio), 자르지 않음
|
|
1658
|
+
/// - cover: 상자 비율로 가운데를 자른 뒤 maxW×maxH 로 줄임. 원본이 상자보다 작으면 확대하지 않고 자른 크기 그대로
|
|
1659
|
+
static func photoThumbnailLayout(width w: Int, height h: Int, options o: PhotoThumbnailOptions) -> (crop: CGRect, size: CGSize) {
|
|
1660
|
+
let dw = Double(w), dh = Double(h)
|
|
1661
|
+
if !o.cover {
|
|
1662
|
+
let r = min(Double(o.maxWidth) / dw, Double(o.maxHeight) / dh, 1)
|
|
1663
|
+
return (CGRect(x: 0, y: 0, width: w, height: h),
|
|
1664
|
+
CGSize(width: max(1, (dw * r).rounded()), height: max(1, (dh * r).rounded())))
|
|
1665
|
+
}
|
|
1666
|
+
let aspect = Double(o.maxWidth) / Double(o.maxHeight)
|
|
1667
|
+
let cropW: Double, cropH: Double
|
|
1668
|
+
if dw / dh > aspect { cropH = dh; cropW = dh * aspect } else { cropW = dw; cropH = dw / aspect }
|
|
1669
|
+
let left = ((dw - cropW) / 2).rounded(), top = ((dh - cropH) / 2).rounded()
|
|
1670
|
+
let crop = CGRect(x: left, y: top, width: min(dw - left, cropW.rounded()), height: min(dh - top, cropH.rounded()))
|
|
1671
|
+
let size = cropW > Double(o.maxWidth)
|
|
1672
|
+
? CGSize(width: o.maxWidth, height: o.maxHeight)
|
|
1673
|
+
: CGSize(width: max(1, cropW.rounded()), height: max(1, cropH.rounded()))
|
|
1674
|
+
return (crop, size)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/// 사진 썸네일을 만들어 결과에 넣을 필드(thumbnailUri/Width/Height/FileSize)를 돌려줌. options 가 없거나 실패하면 빈 딕셔너리.
|
|
1678
|
+
/// image 는 결과 사진(uri)을 만든 것과 같은 UIImage — 원본을 다시 읽지 않음. 새로 그려 인코딩하므로 EXIF·GPS 는 들어가지 않음
|
|
1679
|
+
private func makePhotoThumbnail(_ image: UIImage, options: PhotoThumbnailOptions?, index: Int,
|
|
1680
|
+
timestamp: Int, tempDir: String) -> [String: Any] {
|
|
1681
|
+
guard let o = options else { return [:] }
|
|
1682
|
+
// 방향(imageOrientation) 반영된 픽셀 크기
|
|
1683
|
+
let w = Int((image.size.width * image.scale).rounded()), h = Int((image.size.height * image.scale).rounded())
|
|
1684
|
+
guard w > 0, h > 0 else { return [:] }
|
|
1685
|
+
let (crop, size) = Self.photoThumbnailLayout(width: w, height: h, options: o)
|
|
1686
|
+
let format = UIGraphicsImageRendererFormat()
|
|
1687
|
+
format.scale = 1
|
|
1688
|
+
format.opaque = false
|
|
1689
|
+
format.preferredRange = .standard
|
|
1690
|
+
let sx = size.width / crop.width, sy = size.height / crop.height
|
|
1691
|
+
let thumb = UIGraphicsImageRenderer(size: size, format: format).image { ctx in
|
|
1692
|
+
ctx.cgContext.interpolationQuality = .high
|
|
1693
|
+
image.draw(in: CGRect(x: -crop.minX * sx, y: -crop.minY * sy, width: CGFloat(w) * sx, height: CGFloat(h) * sy))
|
|
1694
|
+
}
|
|
1695
|
+
// 색 프로필(sRGB ICC, 약 3KB)을 붙이지 않도록 장치 RGB 로 태그 — 수 KB 짜리 썸네일에선 용량의 절반 가까이라.
|
|
1696
|
+
// 프로필이 없는 이미지는 sRGB 로 읽히므로 색은 같음
|
|
1697
|
+
let untagged = thumb.cgImage.flatMap { $0.copy(colorSpace: CGColorSpaceCreateDeviceRGB()) }.map { UIImage(cgImage: $0) } ?? thumb
|
|
1698
|
+
// webp 인코딩이 안 되면 jpg 로
|
|
1699
|
+
var data: Data? = nil
|
|
1700
|
+
var ext = "jpg"
|
|
1701
|
+
if o.format == "webp", let d = encodeWebP(image: untagged, quality: o.quality) { data = d; ext = "webp" }
|
|
1702
|
+
if data == nil { data = untagged.jpegData(compressionQuality: o.quality); ext = "jpg" }
|
|
1703
|
+
guard let bytes = data else { return [:] }
|
|
1704
|
+
let url = URL(fileURLWithPath: (tempDir as NSString).appendingPathComponent("turbo_picker_thumb_\(timestamp)_\(index).\(ext)"))
|
|
1705
|
+
do { try bytes.write(to: url, options: [.atomic]) } catch { return [:] }
|
|
1706
|
+
return [
|
|
1707
|
+
"thumbnailUri": url.absoluteString,
|
|
1708
|
+
"thumbnailWidth": Int(size.width),
|
|
1709
|
+
"thumbnailHeight": Int(size.height),
|
|
1710
|
+
"thumbnailFileSize": bytes.count,
|
|
1711
|
+
]
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1624
1714
|
/// 이미지를 지정 포맷으로 저장하고 file:// URI 반환
|
|
1625
1715
|
/// - Parameters:
|
|
1626
1716
|
/// - image: 저장할 UIImage
|
|
@@ -1806,7 +1896,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1806
1896
|
}
|
|
1807
1897
|
|
|
1808
1898
|
|
|
1809
|
-
private func processEditorResult(asset: PHAsset?, croppedImage: UIImage?, filterState: FilterState, caption: String, galleryVC: GalleryViewController?, targetMaxWidth: Int? = nil, targetMaxHeight: Int? = nil, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
1899
|
+
private func processEditorResult(asset: PHAsset?, croppedImage: UIImage?, filterState: FilterState, caption: String, galleryVC: GalleryViewController?, targetMaxWidth: Int? = nil, targetMaxHeight: Int? = nil, thumbnail: PhotoThumbnailOptions? = nil, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
1810
1900
|
let tempDir = prepareCacheDirectory()
|
|
1811
1901
|
// 동영상 1장 편집 → 전송: 편집 화면에서 고른 품질·음소거로 VideoExporter (사진 경로와 같은 결과 형태)
|
|
1812
1902
|
if let phAsset = asset, phAsset.mediaType == .video {
|
|
@@ -1816,6 +1906,8 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1816
1906
|
let maxWidth = targetMaxWidth ?? galleryVC?.maxWidth ?? 1024
|
|
1817
1907
|
let maxHeight = targetMaxHeight ?? galleryVC?.maxHeight ?? 1024
|
|
1818
1908
|
let format = galleryVC?.outputFormat ?? "webp"
|
|
1909
|
+
// openEditor 는 자기 옵션, 갤러리 편집 경로는 openGallery 의 thumbnail
|
|
1910
|
+
let thumbOptions = thumbnail ?? (galleryVC != nil ? galleryThumbnail : nil)
|
|
1819
1911
|
|
|
1820
1912
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
1821
1913
|
let photoManager = PhotoManager.shared
|
|
@@ -1901,6 +1993,9 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1901
1993
|
result["fileExtension"] = finalMeta.fileExtension
|
|
1902
1994
|
result["fileSize"] = finalMeta.fileSize
|
|
1903
1995
|
}
|
|
1996
|
+
// 썸네일: 크롭·그리기·필터가 반영된 결과(uri 와 같은 비트맵)에서
|
|
1997
|
+
result.merge(self.makePhotoThumbnail(finalImageForFormat, options: thumbOptions, index: 0,
|
|
1998
|
+
timestamp: timestamp, tempDir: tempDir)) { $1 }
|
|
1904
1999
|
|
|
1905
2000
|
DispatchQueue.main.async {
|
|
1906
2001
|
self.currentGalleryVC = nil
|
|
@@ -1916,6 +2011,7 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1916
2011
|
let maxWidth = galleryVC.maxWidth
|
|
1917
2012
|
let maxHeight = galleryVC.maxHeight
|
|
1918
2013
|
let format = galleryVC.outputFormat
|
|
2014
|
+
let thumbOptions = galleryThumbnail
|
|
1919
2015
|
|
|
1920
2016
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
1921
2017
|
let photoManager = PhotoManager.shared
|
|
@@ -1962,7 +2058,8 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1962
2058
|
result["fileSize"] = meta.fileSize
|
|
1963
2059
|
}
|
|
1964
2060
|
|
|
1965
|
-
|
|
2061
|
+
let resizedForThumb = (maxWidth > 0 || maxHeight > 0) ? self.resizeImage(finalImage, maxWidth: maxWidth, maxHeight: maxHeight) : nil
|
|
2062
|
+
if let resizedImage = resizedForThumb {
|
|
1966
2063
|
if let resizedUri = self.saveImage(resizedImage, format: format, prefix: "resized", index: index, timestamp: timestamp, tempDir: tempDir) {
|
|
1967
2064
|
result["uri"] = resizedUri
|
|
1968
2065
|
result["width"] = Int(resizedImage.size.width)
|
|
@@ -1973,6 +2070,8 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
1973
2070
|
result["fileSize"] = resizedMeta.fileSize
|
|
1974
2071
|
}
|
|
1975
2072
|
}
|
|
2073
|
+
result.merge(self.makePhotoThumbnail(resizedForThumb ?? finalImage, options: thumbOptions, index: index,
|
|
2074
|
+
timestamp: timestamp, tempDir: tempDir)) { $1 }
|
|
1976
2075
|
|
|
1977
2076
|
resultsLock.lock()
|
|
1978
2077
|
results.append(result)
|
|
@@ -2018,6 +2117,29 @@ class RNAttachmentKit: RCTEventEmitter {
|
|
|
2018
2117
|
}
|
|
2019
2118
|
}
|
|
2020
2119
|
|
|
2120
|
+
/// GalleryOptions.thumbnail / EditorOptions.thumbnail
|
|
2121
|
+
struct PhotoThumbnailOptions {
|
|
2122
|
+
let maxWidth: Int
|
|
2123
|
+
let maxHeight: Int
|
|
2124
|
+
/// true = "cover" (상자를 꽉 채우고 가운데 기준으로 자름), false = "fit"
|
|
2125
|
+
let cover: Bool
|
|
2126
|
+
let quality: CGFloat
|
|
2127
|
+
/// "webp" | "jpg"
|
|
2128
|
+
let format: String
|
|
2129
|
+
|
|
2130
|
+
/// RN 옵션 → 값. maxWidth 가 없거나 0 이하면 nil (썸네일 안 만듦)
|
|
2131
|
+
init?(_ raw: Any?) {
|
|
2132
|
+
guard let d = raw as? NSDictionary, let mw = (d["maxWidth"] as? NSNumber)?.intValue, mw > 0 else { return nil }
|
|
2133
|
+
maxWidth = mw
|
|
2134
|
+
let mh = (d["maxHeight"] as? NSNumber)?.intValue ?? 0
|
|
2135
|
+
maxHeight = mh > 0 ? mh : mw
|
|
2136
|
+
cover = (d["mode"] as? String)?.lowercased() == "cover"
|
|
2137
|
+
quality = CGFloat(min(max((d["quality"] as? NSNumber)?.doubleValue ?? 0.8, 0), 1))
|
|
2138
|
+
let f = (d["format"] as? String)?.lowercased()
|
|
2139
|
+
format = (f == "jpg" || f == "jpeg") ? "jpg" : "webp"
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2021
2143
|
// MARK: - UIColor Extension for Hex Support
|
|
2022
2144
|
extension UIColor {
|
|
2023
2145
|
convenience init?(hexString: String) {
|
|
@@ -2059,6 +2181,10 @@ extension RNAttachmentKit {
|
|
|
2059
2181
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
2060
2182
|
reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
2061
2183
|
let o = options as? [String: Any] ?? [:]
|
|
2184
|
+
// 동영상 항목은 라이선스 키가 있어야
|
|
2185
|
+
if !LicenseGate.isLicensed, (o["items"] as? [[String: Any]] ?? []).compactMap({ MediaGalleryItem(dictionary: $0) }).contains(where: { $0.isVideo }) {
|
|
2186
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
2187
|
+
}
|
|
2062
2188
|
DispatchQueue.main.async {
|
|
2063
2189
|
let config = MediaGalleryConfig(dictionary: o)
|
|
2064
2190
|
let galleryId = config.galleryId
|
|
@@ -2140,6 +2266,9 @@ extension RNAttachmentKit {
|
|
|
2140
2266
|
let galleryId = p["galleryId"] as? String ?? ""
|
|
2141
2267
|
let raw = p["items"] as? [[String: Any]] ?? []
|
|
2142
2268
|
let items = raw.compactMap { MediaGalleryItem(dictionary: $0) }
|
|
2269
|
+
if !LicenseGate.isLicensed, items.contains(where: { $0.isVideo }) {
|
|
2270
|
+
reject(LicenseGate.errorCode, LicenseGate.errorMessage, nil); return
|
|
2271
|
+
}
|
|
2143
2272
|
let page = MediaGalleryPage(items: items, nextCursor: p["nextCursor"] as? String,
|
|
2144
2273
|
hasMore: (p["hasMore"] as? NSNumber)?.boolValue ?? false)
|
|
2145
2274
|
DispatchQueue.main.async {
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>RNAttachmentKit.framework/RNAttachmentKit</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>RNAttachmentKit.framework</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>RNAttachmentKit.framework/RNAttachmentKit</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>RNAttachmentKit.framework</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|