@@ -63,6 +63,9 @@ struct ArchivePhotoDetailFeature {
6363 case toggleDropDownMenu
6464 case closeDropDownMenu
6565
66+ case onTapShareToInstagramStory
67+ case instagramImageFetchResponse( Result < UIImage , Error > )
68+
6669 case delegate( Delegate )
6770 enum Delegate {
6871 case showToast( NekiToastItem )
@@ -175,24 +178,24 @@ struct ArchivePhotoDetailFeature {
175178 switch delegateAction {
176179
177180 case let . didSelectAlbum( albumId) :
178- // let photoId = state.currentItemID
179- // state.albumSelection = nil
180- // state.isLoading = true
181- //
181+ // let photoId = state.currentItemID
182+ // state.albumSelection = nil
183+ // state.isLoading = true
184+ //
182185 // TODO: - 어떤 식으로 업로드 할 건지 서버측과 논의 필요함
183186 // 그냥 킹피셔로 데이터 추출해서 새로운 파일로 업로드하는 것도 가능하긴 한데, 흠
184- // return .run { send in
185- // await send(.addToAlbumResponse(Result {
186- // let isFavorite = albumId == -1
187- // let targetFolderId = isFavorite ? nil : albumId
188- //
189- // try await archiveClient.registerPhotos(
190- // folderId: targetFolderId,
191- // uploads: [(mediaID: photoId, memo: String?.none, uploadMethod: PhotoUploadMethod.direct)],
192- // favorite: isFavorite
193- // )
194- // }))
195- // }
187+ // return .run { send in
188+ // await send(.addToAlbumResponse(Result {
189+ // let isFavorite = albumId == -1
190+ // let targetFolderId = isFavorite ? nil : albumId
191+ //
192+ // try await archiveClient.registerPhotos(
193+ // folderId: targetFolderId,
194+ // uploads: [(mediaID: photoId, memo: String?.none, uploadMethod: PhotoUploadMethod.direct)],
195+ // favorite: isFavorite
196+ // )
197+ // }))
198+ // }
196199 return . none
197200
198201 case . didTapCancel:
@@ -277,6 +280,69 @@ struct ArchivePhotoDetailFeature {
277280 case . deletePhotoResponse( . failure) :
278281 return . send( . delegate( . showToast( NekiToastItem ( " 사진을 삭제하지 못했어요 " , style: . error) ) ) )
279282
283+ case . onTapShareToInstagramStory:
284+ state. showDropDownMenu = false
285+ guard let url = state. currentItem? . imageURL else { return . none }
286+
287+ state. isLoading = true
288+
289+ return . run { send in
290+ do {
291+ let image = try await withCheckedThrowingContinuation { continuation in
292+ KingfisherManager . shared. retrieveImage (
293+ with: url,
294+ options: [ . cacheOriginalImage]
295+ ) { result in
296+ switch result {
297+ case . success( let value) :
298+ continuation. resume ( returning: value. image)
299+ case . failure( let error) :
300+ continuation. resume ( throwing: error)
301+ }
302+ }
303+ }
304+ await send ( . instagramImageFetchResponse( . success( image) ) )
305+ } catch {
306+ await send ( . instagramImageFetchResponse( . failure( error) ) )
307+ }
308+ }
309+
310+ case let . instagramImageFetchResponse( . success( image) ) :
311+ state. isLoading = false
312+ return . run { send in
313+ // 이미지를 클립보드에 담고 인스타그램 앱 열기 (MainActor에서 실행)
314+ let isShared = await MainActor . run { ( ) -> Bool in
315+ guard let urlScheme = URL ( string: " instagram-stories://share?source_application=Neki " ) else { return false }
316+
317+ // 인스타그램 앱이 설치되어 있는지 확인
318+ if UIApplication . shared. canOpenURL ( urlScheme) {
319+ if let imageData = image. pngData ( ) {
320+ // 인스타 스토리 배경으로 이미지 전달
321+ let pasteboardItems : [ [ String : Any ] ] = [
322+ [ " com.instagram.sharedSticker.backgroundImage " : imageData]
323+ ]
324+ let pasteboardOptions = [
325+ UIPasteboard . OptionsKey. expirationDate: Date ( ) . addingTimeInterval ( 60 * 5 )
326+ ]
327+
328+ UIPasteboard . general. setItems ( pasteboardItems, options: pasteboardOptions)
329+ UIApplication . shared. open ( urlScheme, options: [ : ] , completionHandler: nil )
330+ return true
331+ }
332+ }
333+ return false
334+ }
335+
336+ // 설치되어 있지 않거나 실패한 경우 토스트
337+ if !isShared {
338+ await send ( . delegate( . showToast( NekiToastItem ( " 인스타그램 앱이 설치되어 있지 않아요 " , style: . error) ) ) )
339+ }
340+ }
341+
342+ case . instagramImageFetchResponse( . failure) :
343+ state. isLoading = false
344+ return . send( . delegate( . showToast( NekiToastItem ( " 이미지를 불러오지 못했어요 " , style: . error) ) ) )
345+
280346 default :
281347 return . none
282348 }
0 commit comments