From 988a8f92fdc5b1e53da748254f514d9b00926804 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:56:06 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Feat:=20NamoEntireImageView=20UI=20?= =?UTF-8?q?=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/NamoEntireImageView.swift | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift diff --git a/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift b/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift new file mode 100644 index 00000000..9a8bd80c --- /dev/null +++ b/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift @@ -0,0 +1,88 @@ +// +// NamoEntireImageView.swift +// SharedDesignSystem +// +// Created by 박민서 on 12/11/24. +// + +import SwiftUI + +public struct NamoEntireImageView: View { + @State var image: Image = Image(asset: SharedDesignSystemAsset.Assets.noGroup) + @State var showToast: Bool = true + @State var title: String = "이미지가 저장되었습니다." + + public var body: some View { + ZStack() { + image + .resizable() + .scaledToFit() + .ignoresSafeArea(.container, edges: .bottom) + + VStack { + TopBar(totalImgCount: 2, currentImgIndex: .constant(1)) + Spacer() + } + } + .background(.black) + .namoToastView( + isPresented: $showToast, + title: title, + isTabBarScreen: false + ) + } +} + +// MARK: Top Bar +private extension NamoEntireImageView { + func TopBar(totalImgCount: Int, currentImgIndex: Binding) -> some View { + HStack { + // Back button + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icArrowLeft) + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundStyle(.white) + .frame(width: 32, height: 32) + }) + + Spacer() + + // Image number + HStack(spacing: 10) { + Text("\(currentImgIndex.wrappedValue)") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(.white) + + Text("/") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(Color.textPlaceholder) + + Text("\(totalImgCount)") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(Color.textPlaceholder) + } + + Spacer() + + // Download button + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icDownload) + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundStyle(.white) + .frame(width: 24, height: 24) + }) + } + .padding(.horizontal, 24) + .padding(.vertical, 14) + .frame(maxWidth: .infinity) + .background(Color.colorBlack.opacity(0.5)) + } +} + +#Preview { + NamoEntireImageView() +} From 3650525ad59901f052a820d527735ecbae8a0198 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:05:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Feat:=20Namo->HomeEntireImageView=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/HomeEntireImageView.swift | 185 ++++++++++++++++++ .../ImageView/NamoEntireImageView.swift | 88 --------- 2 files changed, 185 insertions(+), 88 deletions(-) create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift delete mode 100644 Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift new file mode 100644 index 00000000..fdb08933 --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift @@ -0,0 +1,185 @@ +// +// NamoEntireImageView.swift +// SharedDesignSystem +// +// Created by 박민서 on 12/11/24. +// + +import SwiftUI +import ComposableArchitecture +import SharedDesignSystem + +// MARK: Reducer +@Reducer +public struct HomeEntireImageStore { + + public init() {} + + @ObservableState + public struct State: Equatable { + public init(imgDataList: [Data], currentPage: Int = 0) { + self.imgDataList = imgDataList + self.currentPage = currentPage + } + + let imgDataList: [Data] + var currentPage: Int + // 토스트 + var showToast: Bool = false + var toast: Toast = .none + } + + public enum Action: BindableAction { + case binding(BindingAction) + case tapBackButton + case tapDownloadButton + case pageChanged(page: Int) + case showToast(Toast) + } + + public var body: some ReducerOf { + BindingReducer() + + Reduce { state, action in + switch action { + + case .binding: + return .none + + case .tapBackButton: + print("dismiss") + return .none + case .tapDownloadButton: + print("download") + return .send(.showToast(.saveSuccess)) + + case let .pageChanged(newPage): + state.currentPage = newPage + return .none + + case .showToast(let toast): + state.toast = toast + state.showToast = true + return .none + } + } + } +} + +extension HomeEntireImageStore { + public enum Toast { + case saveSuccess + case saveFailed + case none + + var content: String { + switch self { + case .saveSuccess: + return "이미지가 저장되었습니다." + case .saveFailed: + return "이미지 저장에 실패했습니다.\n다시 시도해주세요." + case .none: + return "" + } + } + } +} + +// MARK: NamoEntireImageView +public struct HomeEntireImageView: View { + + @Perception.Bindable var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithPerceptionTracking { + ZStack() { +// TabView(selection: Binding( +// get: { store.currentPage }, +// set: { store.send(.pageChanged($0)) } +// )) { +// ForEach(store.imgDataList) { imageData in +// if let uiImage = UIImage(data: imageData.imageData) { +// Image(uiImage: uiImage) +// .resizable() +// .scaledToFit() +// .tag(store.imgDataList.firstIndex(of: imageData) ?? 0) +// } +// } +// } +// .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) +// .ignoresSafeArea(.container, edges: .bottom) + + VStack { + TopBar(totalImgCount: store.imgDataList.count, currentImgIndex: $store.currentPage) + Spacer() + } + } + .background(.black) + .namoToastView( + isPresented: $store.showToast, + title: store.toast.content, + isTabBarScreen: false + ) + } + } +} + +// MARK: Top Bar +private extension HomeEntireImageView { + func TopBar(totalImgCount: Int, currentImgIndex: Binding) -> some View { + HStack { + // Back button + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icArrowLeft) + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundStyle(.white) + .frame(width: 32, height: 32) + }) + + Spacer() + + // Image number + HStack(spacing: 10) { + Text("\(currentImgIndex.wrappedValue)") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(.white) + + Text("/") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(Color.textPlaceholder) + + Text("\(totalImgCount)") + .font(.pretendard(.bold, size: 18)) + .foregroundStyle(Color.textPlaceholder) + } + + Spacer() + + // Download button + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icDownload) + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundStyle(.white) + .frame(width: 24, height: 24) + }) + } + .padding(.horizontal, 24) + .padding(.vertical, 14) + .frame(maxWidth: .infinity) + .background(Color.colorBlack.opacity(0.5)) + } +} + +#Preview { + HomeEntireImageView(store: .init(initialState: HomeEntireImageStore.State(imgDataList: [])) { + HomeEntireImageStore() + }) +} diff --git a/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift b/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift deleted file mode 100644 index 9a8bd80c..00000000 --- a/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/ImageView/NamoEntireImageView.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// NamoEntireImageView.swift -// SharedDesignSystem -// -// Created by 박민서 on 12/11/24. -// - -import SwiftUI - -public struct NamoEntireImageView: View { - @State var image: Image = Image(asset: SharedDesignSystemAsset.Assets.noGroup) - @State var showToast: Bool = true - @State var title: String = "이미지가 저장되었습니다." - - public var body: some View { - ZStack() { - image - .resizable() - .scaledToFit() - .ignoresSafeArea(.container, edges: .bottom) - - VStack { - TopBar(totalImgCount: 2, currentImgIndex: .constant(1)) - Spacer() - } - } - .background(.black) - .namoToastView( - isPresented: $showToast, - title: title, - isTabBarScreen: false - ) - } -} - -// MARK: Top Bar -private extension NamoEntireImageView { - func TopBar(totalImgCount: Int, currentImgIndex: Binding) -> some View { - HStack { - // Back button - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icArrowLeft) - .renderingMode(.template) - .resizable() - .aspectRatio(contentMode: .fit) - .foregroundStyle(.white) - .frame(width: 32, height: 32) - }) - - Spacer() - - // Image number - HStack(spacing: 10) { - Text("\(currentImgIndex.wrappedValue)") - .font(.pretendard(.bold, size: 18)) - .foregroundStyle(.white) - - Text("/") - .font(.pretendard(.bold, size: 18)) - .foregroundStyle(Color.textPlaceholder) - - Text("\(totalImgCount)") - .font(.pretendard(.bold, size: 18)) - .foregroundStyle(Color.textPlaceholder) - } - - Spacer() - - // Download button - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icDownload) - .renderingMode(.template) - .resizable() - .aspectRatio(contentMode: .fit) - .foregroundStyle(.white) - .frame(width: 24, height: 24) - }) - } - .padding(.horizontal, 24) - .padding(.vertical, 14) - .frame(maxWidth: .infinity) - .background(Color.colorBlack.opacity(0.5)) - } -} - -#Preview { - NamoEntireImageView() -} From f62c4c46b5577e19244ec24a3368e1a34f94c556 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:11:23 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Feat:=20HomeEntireImageStore=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/HomeEntireImageStore.swift | 84 +++++++++++++ .../ImageView/HomeEntireImageView.swift | 119 +++++------------- 2 files changed, 114 insertions(+), 89 deletions(-) create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift new file mode 100644 index 00000000..f92690da --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift @@ -0,0 +1,84 @@ +// +// HomeEntireImageStore.swift +// FeatureHome +// +// Created by 박민서 on 12/11/24. +// + +import Foundation +import ComposableArchitecture + +@Reducer +public struct HomeEntireImageStore { + + public init() {} + + @ObservableState + public struct State: Equatable { + public init(imgDataList: [Data], currentPage: Int = 0) { + self.imgDataList = IdentifiedArray(uniqueElements: imgDataList.map { IdentifiableData(data: $0) }) + self.currentPage = currentPage + } + + let imgDataList: IdentifiedArrayOf + var currentPage: Int + // 토스트 + var showToast: Bool = false + var toast: Toast = .none + } + + public enum Action: BindableAction { + case binding(BindingAction) + case tapBackButton + case tapDownloadButton + case pageChanged(page: Int) + case showToast(Toast) + } + + public var body: some ReducerOf { + BindingReducer() + + Reduce { state, action in + switch action { + + case .binding: + return .none + + case .tapBackButton: + print("dismiss") + return .none + case .tapDownloadButton: + print("download") + return .send(.showToast(.saveSuccess)) + + case let .pageChanged(newPage): + state.currentPage = newPage + return .none + + case .showToast(let toast): + state.toast = toast + state.showToast = true + return .none + } + } + } +} + +extension HomeEntireImageStore { + public enum Toast { + case saveSuccess + case saveFailed + case none + + var content: String { + switch self { + case .saveSuccess: + return "이미지가 저장되었습니다." + case .saveFailed: + return "이미지 저장에 실패했습니다.\n다시 시도해주세요." + case .none: + return "" + } + } + } +} diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift index fdb08933..dd091148 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift @@ -9,83 +9,17 @@ import SwiftUI import ComposableArchitecture import SharedDesignSystem -// MARK: Reducer -@Reducer -public struct HomeEntireImageStore { +public struct IdentifiableData: Identifiable, Equatable { + public let id: UUID + public let data: Data - public init() {} - - @ObservableState - public struct State: Equatable { - public init(imgDataList: [Data], currentPage: Int = 0) { - self.imgDataList = imgDataList - self.currentPage = currentPage - } - - let imgDataList: [Data] - var currentPage: Int - // 토스트 - var showToast: Bool = false - var toast: Toast = .none - } - - public enum Action: BindableAction { - case binding(BindingAction) - case tapBackButton - case tapDownloadButton - case pageChanged(page: Int) - case showToast(Toast) - } - - public var body: some ReducerOf { - BindingReducer() - - Reduce { state, action in - switch action { - - case .binding: - return .none - - case .tapBackButton: - print("dismiss") - return .none - case .tapDownloadButton: - print("download") - return .send(.showToast(.saveSuccess)) - - case let .pageChanged(newPage): - state.currentPage = newPage - return .none - - case .showToast(let toast): - state.toast = toast - state.showToast = true - return .none - } - } + public init(id: UUID = UUID(), data: Data) { + self.id = id + self.data = data } } -extension HomeEntireImageStore { - public enum Toast { - case saveSuccess - case saveFailed - case none - - var content: String { - switch self { - case .saveSuccess: - return "이미지가 저장되었습니다." - case .saveFailed: - return "이미지 저장에 실패했습니다.\n다시 시도해주세요." - case .none: - return "" - } - } - } -} - -// MARK: NamoEntireImageView +// MARK: HomeEntireImageView public struct HomeEntireImageView: View { @Perception.Bindable var store: StoreOf @@ -97,21 +31,7 @@ public struct HomeEntireImageView: View { public var body: some View { WithPerceptionTracking { ZStack() { -// TabView(selection: Binding( -// get: { store.currentPage }, -// set: { store.send(.pageChanged($0)) } -// )) { -// ForEach(store.imgDataList) { imageData in -// if let uiImage = UIImage(data: imageData.imageData) { -// Image(uiImage: uiImage) -// .resizable() -// .scaledToFit() -// .tag(store.imgDataList.firstIndex(of: imageData) ?? 0) -// } -// } -// } -// .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) -// .ignoresSafeArea(.container, edges: .bottom) + ImageCarouselView(store: store) VStack { TopBar(totalImgCount: store.imgDataList.count, currentImgIndex: $store.currentPage) @@ -146,7 +66,7 @@ private extension HomeEntireImageView { // Image number HStack(spacing: 10) { - Text("\(currentImgIndex.wrappedValue)") + Text("\(currentImgIndex.wrappedValue + 1)") // 표시는 index + 1 .font(.pretendard(.bold, size: 18)) .foregroundStyle(.white) @@ -178,6 +98,27 @@ private extension HomeEntireImageView { } } +// MARK: ImageCarouselView +private extension HomeEntireImageView { + func ImageCarouselView(store: StoreOf) -> some View { + TabView(selection: Binding( + get: { store.currentPage }, + set: { store.send(.pageChanged(page: $0)) } + )) { + ForEach(store.imgDataList, id: \.id) { imageData in + if let uiImage = UIImage(data: imageData.data) { + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .tag(store.imgDataList.firstIndex(of: imageData) ?? 0) + } + } + } + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + .ignoresSafeArea(.container, edges: .bottom) + } +} + #Preview { HomeEntireImageView(store: .init(initialState: HomeEntireImageStore.State(imgDataList: [])) { HomeEntireImageStore() From a5e969add479396139a71d2751786fc57421800f Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:15:55 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Feat:=20IdentifableData=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/HomeEntireImageStore.swift | 1 + .../ImageView/HomeEntireImageView.swift | 12 +----------- .../Util/Sources/Model/IdentifiableData.swift | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 Namo_SwiftUI/Projects/Shared/Util/Sources/Model/IdentifiableData.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift index f92690da..84bce83c 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift @@ -7,6 +7,7 @@ import Foundation import ComposableArchitecture +import SharedUtil @Reducer public struct HomeEntireImageStore { diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift index dd091148..811fe262 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift @@ -8,18 +8,8 @@ import SwiftUI import ComposableArchitecture import SharedDesignSystem +import SharedUtil -public struct IdentifiableData: Identifiable, Equatable { - public let id: UUID - public let data: Data - - public init(id: UUID = UUID(), data: Data) { - self.id = id - self.data = data - } -} - -// MARK: HomeEntireImageView public struct HomeEntireImageView: View { @Perception.Bindable var store: StoreOf diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/IdentifiableData.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/IdentifiableData.swift new file mode 100644 index 00000000..d45cd03e --- /dev/null +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/IdentifiableData.swift @@ -0,0 +1,18 @@ +// +// IdentifiableData.swift +// SharedUtil +// +// Created by 박민서 on 12/11/24. +// + +import Foundation + +public struct IdentifiableData: Identifiable, Equatable { + public let id: UUID + public let data: Data + + public init(id: UUID = UUID(), data: Data) { + self.id = id + self.data = data + } +} From fc68d232acd1d3b8863576f3696a1cf85a2d4724 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:03:52 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Feat:=20Image=20Download=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/HomeEntireImageStore.swift | 44 ++++++++++++++++++- .../ImageView/HomeEntireImageView.swift | 8 +++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift index 84bce83c..102b2caa 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift @@ -9,6 +9,32 @@ import Foundation import ComposableArchitecture import SharedUtil +import Photos +import UIKit + +enum PhotoLibraryError: Error { + case unauthorized + case unknown(Error?) +} + +func saveImageToPhotoLibrary(_ image: UIImage) async throws { + let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) + guard status == .authorized else { throw PhotoLibraryError.unauthorized } + + try await withCheckedThrowingContinuation { continuation in + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAsset(from: image) + }) { success, error in + if success { + continuation.resume() + } else { + continuation.resume(throwing: PhotoLibraryError.unknown(error)) + } + } + } +} + + @Reducer public struct HomeEntireImageStore { @@ -23,6 +49,8 @@ public struct HomeEntireImageStore { let imgDataList: IdentifiedArrayOf var currentPage: Int + var curretImgData: IdentifiableData? { imgDataList[currentPage] } + // 토스트 var showToast: Bool = false var toast: Toast = .none @@ -32,6 +60,7 @@ public struct HomeEntireImageStore { case binding(BindingAction) case tapBackButton case tapDownloadButton + case downloadImage(imgData: Data) case pageChanged(page: Int) case showToast(Toast) } @@ -49,8 +78,19 @@ public struct HomeEntireImageStore { print("dismiss") return .none case .tapDownloadButton: - print("download") - return .send(.showToast(.saveSuccess)) + let imgData = state.imgDataList[state.currentPage] + return .send(.downloadImage(imgData: imgData.data)) + + case .downloadImage(let imgData): + return .run { send in + do { + guard let image = UIImage(data: imgData) else { throw NSError(domain: "imgData convert failed", code: 1) } + try await saveImageToPhotoLibrary(image) + await send(.showToast(.saveSuccess)) + } catch { + await send(.showToast(.saveFailed)) + } + } case let .pageChanged(newPage): state.currentPage = newPage diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift index 811fe262..c82c825c 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageView.swift @@ -43,7 +43,9 @@ private extension HomeEntireImageView { func TopBar(totalImgCount: Int, currentImgIndex: Binding) -> some View { HStack { // Back button - Button(action: {}, label: { + Button(action: { + store.send(.tapBackButton) + }, label: { Image(asset: SharedDesignSystemAsset.Assets.icArrowLeft) .renderingMode(.template) .resizable() @@ -72,7 +74,9 @@ private extension HomeEntireImageView { Spacer() // Download button - Button(action: {}, label: { + Button(action: { + store.send(.tapDownloadButton) + }, label: { Image(asset: SharedDesignSystemAsset.Assets.icDownload) .renderingMode(.template) .resizable() From 81566ce37c2b05a03b57977e749b762645526cbe Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:06:58 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Feat:=20Photos=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20Utils=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageView/HomeEntireImageStore.swift | 28 +---------------- .../Shared/Util/Sources/Utils/Photos.swift | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 Namo_SwiftUI/Projects/Shared/Util/Sources/Utils/Photos.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift index 102b2caa..598a9847 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/ImageView/HomeEntireImageStore.swift @@ -5,35 +5,9 @@ // Created by 박민서 on 12/11/24. // -import Foundation import ComposableArchitecture -import SharedUtil - -import Photos import UIKit - -enum PhotoLibraryError: Error { - case unauthorized - case unknown(Error?) -} - -func saveImageToPhotoLibrary(_ image: UIImage) async throws { - let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) - guard status == .authorized else { throw PhotoLibraryError.unauthorized } - - try await withCheckedThrowingContinuation { continuation in - PHPhotoLibrary.shared().performChanges({ - PHAssetChangeRequest.creationRequestForAsset(from: image) - }) { success, error in - if success { - continuation.resume() - } else { - continuation.resume(throwing: PhotoLibraryError.unknown(error)) - } - } - } -} - +import SharedUtil @Reducer public struct HomeEntireImageStore { diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Utils/Photos.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Utils/Photos.swift new file mode 100644 index 00000000..ed57b025 --- /dev/null +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Utils/Photos.swift @@ -0,0 +1,31 @@ +// +// Photos.swift +// SharedUtil +// +// Created by 박민서 on 12/11/24. +// + +import Photos +import UIKit + +public enum PhotoLibraryError: Error { + case unauthorized + case unknown(Error?) +} + +public func saveImageToPhotoLibrary(_ image: UIImage) async throws { + let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) + guard status == .authorized else { throw PhotoLibraryError.unauthorized } + + try await withCheckedThrowingContinuation { continuation in + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAsset(from: image) + }) { success, error in + if success { + continuation.resume() + } else { + continuation.resume(throwing: PhotoLibraryError.unknown(error)) + } + } + } +}