From 384ef23eb1dbeaf913a7abee9e3cc27ca24beda9 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 00:49:27 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=ED=85=9C=ED=94=8C=EB=A6=BF=20?= =?UTF-8?q?=ED=94=84=EB=A6=AC=EB=B7=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=99=BC=EC=AA=BD=20=EC=B9=98=EC=9A=B0=EC=B9=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 부모 VStack(alignment: .leading)에 의해 templatePreview가 leading 정렬되던 문제 - .frame(maxWidth: .infinity) 추가로 해결 --- Keychy/Keychy.xcodeproj/project.pbxproj | 8 ++++---- .../Shared/Components/TemplatePreviewComponents.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Keychy/Keychy.xcodeproj/project.pbxproj b/Keychy/Keychy.xcodeproj/project.pbxproj index f598c075..849310f9 100644 --- a/Keychy/Keychy.xcodeproj/project.pbxproj +++ b/Keychy/Keychy.xcodeproj/project.pbxproj @@ -3235,7 +3235,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.KeychyOfficial.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3284,7 +3284,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.KeychyOfficial.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3324,7 +3324,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.KeychyOfficial.app.WidgetKeychy; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3367,7 +3367,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.KeychyOfficial.app.WidgetKeychy; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Keychy/Keychy/Presentation/KeyringMaker/Shared/Components/TemplatePreviewComponents.swift b/Keychy/Keychy/Presentation/KeyringMaker/Shared/Components/TemplatePreviewComponents.swift index a1d358f1..aa512916 100644 --- a/Keychy/Keychy/Presentation/KeyringMaker/Shared/Components/TemplatePreviewComponents.swift +++ b/Keychy/Keychy/Presentation/KeyringMaker/Shared/Components/TemplatePreviewComponents.swift @@ -201,7 +201,7 @@ extension TemplatePreviewBody { LoadingAlert(type: .short40, message: nil) } } - .frame(maxHeight: 500) + .frame(maxWidth: .infinity, maxHeight: 500) } /// 템플릿 정보 섹션 From e7ed43ed6d499b18c6a1d7fc867bbe5f9e4ca397 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 01:00:56 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EB=AD=89=EC=B9=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=EB=B7=B0=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=8B=9C=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=82=B4=EB=A6=AC?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 배경/카라비너 시트 올라온상태에서 화면 탭하면 닫기 - 키링 걸기 +버튼 누름 -> 뒤에 배경/카라비너 시트가 올라가있으면 내림 --- .../Create/BundleCreateView+SelectSheet.swift | 10 +++++++++- .../Bundle/Views/Create/BundleCreateView.swift | 3 +++ .../Bundle/Views/Edit/BundleEditView.swift | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift index 7715e2fb..d0a62778 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift @@ -29,7 +29,15 @@ extension BundleCreateView { isSelected: selectedPosition == index, action: { selectedPosition = index - showKeyringSheet = true + if showItemSheet { + // 배경/카라비너 시트 닫기 → 닫힌 후 키링 시트 표시 + showItemSheet = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + showKeyringSheet = true + } + } else { + showKeyringSheet = true + } } ) .position(x: viewX, y: viewY) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift index 2c40c0b5..95b64100 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift @@ -97,6 +97,9 @@ struct BundleCreateView: View { keyringButtons(carabiner: cb.carabiner) } .blur(radius: showPurchaseSuccessAlert || isCapturing ? 10 : 0) + .onTapGesture { + if showItemSheet { showItemSheet = false } + } // 하단 셀렉터 + 시트 sheetContent diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift index 427a2816..6eca36be 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift @@ -171,6 +171,9 @@ struct BundleEditView: View { // navigationBar customNavigationBar } + .onTapGesture { + if showItemSheet { showItemSheet = false } + } } // MARK: - 키링 편집 씬 뷰 @@ -228,8 +231,18 @@ struct BundleEditView: View { isSelected: selectedPosition == index, action: { selectedPosition = index - withAnimation(.easeInOut) { - showSelectKeyringSheet = true + if showItemSheet { + // 배경/카라비너 시트 닫기 → 닫힌 후 키링 시트 표시 + showItemSheet = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + withAnimation(.easeInOut) { + showSelectKeyringSheet = true + } + } + } else { + withAnimation(.easeInOut) { + showSelectKeyringSheet = true + } } } ) From 4b32510ce0fb7b6a7bb801846f2cd9046c4771e4 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 02:00:40 +0900 Subject: [PATCH 3/7] =?UTF-8?q?perf:=20=EB=AD=89=EC=B9=98=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD/=EC=B9=B4=EB=9D=BC=EB=B9=84=EB=84=88=20=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시트를 if 조건부 렌더링에서 offset 기반으로 전환하여 열기 반응 속도 향상 - matchedGeometryEffect로 셀렉터 버튼 전환 애니메이션 잔상 제거 - 배경/카라비너 콘텐츠를 ZStack+opacity로 유지하여 탭 전환 시 로딩 깜빡임 제거 - 시트 열릴 때 sheetHeight 리셋으로 드래그 후 재오픈 버그 수정 - 키링 +버튼 클릭 시 배경/카라비너 시트 자동 닫힘 - 화면 탭으로 시트 닫기 기능 추가 --- .../Create/BundleCreateView+SelectSheet.swift | 58 +++++++++++-------- .../Views/Create/BundleCreateView.swift | 5 +- .../Edit/BundleEditView+SelectSheet.swift | 56 ++++++++++-------- .../Bundle/Views/Edit/BundleEditView.swift | 4 +- 4 files changed, 71 insertions(+), 52 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift index d0a62778..4fd64e35 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+SelectSheet.swift @@ -53,42 +53,46 @@ extension BundleCreateView { var sheetContent: some View { ZStack(alignment: .bottom) { Color.clear - - if showItemSheet { - // 시트가 있을 때: 셀렉터 + 시트가 함께 움직임 - VStack(spacing: 0) { - BundleSheetToggleButtons( - showItemSheet: $showItemSheet, - isBackgroundMode: $isBackgroundMode - ) - .padding(.bottom, 10) - - DraggableSheet( - sheetHeight: $sheetHeight, - header: BundleSheetFilterBar(viewModel: bundleVM), - content: itemSheetContent, - onDismiss: { - showItemSheet = false - } - ) + + // 시트 레이어 (항상 존재, 오프셋으로 숨김 → 즉시 반응) + DraggableSheet( + sheetHeight: $sheetHeight, + header: BundleSheetFilterBar(viewModel: bundleVM), + content: itemSheetContent, + onDismiss: { + showItemSheet = false } - .transition(.move(edge: .bottom)) + ) + .offset(y: showItemSheet ? 0 : sheetHeight) + .allowsHitTesting(showItemSheet) + + // 버튼 레이어 (matchedGeometryEffect로 위치만 보간) + if showItemSheet { + BundleSheetToggleButtons( + showItemSheet: $showItemSheet, + isBackgroundMode: $isBackgroundMode + ) + .matchedGeometryEffect(id: "toggleButtons", in: sheetButtonNamespace) + .padding(.bottom, sheetHeight + 10) } else { - // 시트가 없을 때: 셀렉터만 하단에 고정 BundleSheetToggleButtons( showItemSheet: $showItemSheet, isBackgroundMode: $isBackgroundMode ) + .matchedGeometryEffect(id: "toggleButtons", in: sheetButtonNamespace) .padding(.bottom, 50) - .transition(.identity) } } - .animation(.easeInOut(duration: 0.25), value: showItemSheet) + .animation(.easeOut(duration: 0.2), value: showItemSheet) + .onChange(of: showItemSheet) { _, isShowing in + if isShowing { + sheetHeight = UIScreen.main.bounds.height * 0.4 + } + } } - @ViewBuilder var itemSheetContent: some View { - if isBackgroundMode { + ZStack(alignment: .top) { SelectBackgroundSheet( viewModel: bundleVM, selectedBG: bundleVM.newSelectedBackground, @@ -96,7 +100,9 @@ extension BundleCreateView { bundleVM.newSelectedBackground = bg } ) - } else { + .opacity(isBackgroundMode ? 1 : 0) + .allowsHitTesting(isBackgroundMode) + SelectCarabinerSheet( viewModel: bundleVM, selectedCarabiner: bundleVM.newSelectedCarabiner, @@ -105,6 +111,8 @@ extension BundleCreateView { bundleVM.newSelectedCarabiner = carabiner } ) + .opacity(isBackgroundMode ? 0 : 1) + .allowsHitTesting(!isBackgroundMode) } } diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift index 95b64100..41d60e2f 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView.swift @@ -24,12 +24,13 @@ struct BundleCreateView: View { @Bindable var bundleVM: BundleViewModel // 시트 활성화 상태 + @Namespace var sheetButtonNamespace @State var showItemSheet: Bool = false @State var isBackgroundMode: Bool = true // true: 배경, false: 카라비너 @State var showKeyringSheet: Bool = false - // 시트 높이 - @State var sheetHeight: CGFloat = 360 + // 시트 높이 (DraggableSheet.onAppear에서 mediumHeight로 갱신됨) + @State var sheetHeight: CGFloat = UIScreen.main.bounds.height * 0.4 // 키링 선택 상태 @State var selectedKeyrings: [Int: Keyring] = [:] diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+SelectSheet.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+SelectSheet.swift index 2105941e..7088a02d 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+SelectSheet.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+SelectSheet.swift @@ -12,41 +12,45 @@ extension BundleEditView { ZStack(alignment: .bottom) { Color.clear - if showItemSheet { - // 시트가 있을 때: 셀렉터 + 시트가 함께 움직임 - VStack(spacing: 0) { - BundleSheetToggleButtons( - showItemSheet: $showItemSheet, - isBackgroundMode: $isBackgroundMode - ) - .padding(.bottom, 10) - - DraggableSheet( - sheetHeight: $sheetHeight, - header: BundleSheetFilterBar(viewModel: bundleVM), - content: itemSheetContent, - onDismiss: { - showItemSheet = false - } - ) + // 시트 레이어 (항상 존재, 오프셋으로 숨김 → 즉시 반응) + DraggableSheet( + sheetHeight: $sheetHeight, + header: BundleSheetFilterBar(viewModel: bundleVM), + content: itemSheetContent, + onDismiss: { + showItemSheet = false } - .transition(.move(edge: .bottom)) + ) + .offset(y: showItemSheet ? 0 : sheetHeight) + .allowsHitTesting(showItemSheet) + + // 버튼 레이어 (matchedGeometryEffect로 위치만 보간) + if showItemSheet { + BundleSheetToggleButtons( + showItemSheet: $showItemSheet, + isBackgroundMode: $isBackgroundMode + ) + .matchedGeometryEffect(id: "toggleButtons", in: sheetButtonNamespace) + .padding(.bottom, sheetHeight + 10) } else { - // 시트가 없을 때: 셀렉터만 하단에 고정 BundleSheetToggleButtons( showItemSheet: $showItemSheet, isBackgroundMode: $isBackgroundMode ) + .matchedGeometryEffect(id: "toggleButtons", in: sheetButtonNamespace) .padding(.bottom, 50) - .transition(.identity) } } - .animation(.easeInOut(duration: 0.25), value: showItemSheet) + .animation(.easeOut(duration: 0.2), value: showItemSheet) + .onChange(of: showItemSheet) { _, isShowing in + if isShowing { + sheetHeight = UIScreen.main.bounds.height * 0.4 + } + } } - @ViewBuilder private var itemSheetContent: some View { - if isBackgroundMode { + ZStack(alignment: .top) { SelectBackgroundSheet( viewModel: bundleVM, selectedBG: bundleVM.newSelectedBackground, @@ -54,7 +58,9 @@ extension BundleEditView { bundleVM.newSelectedBackground = bg } ) - } else { + .opacity(isBackgroundMode ? 1 : 0) + .allowsHitTesting(isBackgroundMode) + SelectCarabinerSheet( viewModel: bundleVM, selectedCarabiner: bundleVM.newSelectedCarabiner, @@ -63,6 +69,8 @@ extension BundleEditView { showChangeCarabinerAlert = true } ) + .opacity(isBackgroundMode ? 0 : 1) + .allowsHitTesting(!isBackgroundMode) } } diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift index 6eca36be..848e16cb 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift @@ -25,6 +25,7 @@ struct BundleEditView: View { @State var isCapturing: Bool = false // MARK: - Sheet + @Namespace var sheetButtonNamespace @State var showItemSheet: Bool = false @State var isBackgroundMode: Bool = true // true: 배경, false: 카라비너 @State var showPurchaseSheet = false @@ -52,7 +53,8 @@ struct BundleEditView: View { ] // 시트 높이 (화면의 약 43%에 해당) - @State var sheetHeight: CGFloat = 360 + // 시트 높이 (DraggableSheet.onAppear에서 mediumHeight로 갱신됨) + @State var sheetHeight: CGFloat = UIScreen.main.bounds.height * 0.4 @State var purchasesSuccessScale: CGFloat = 0.3 @State var purchaseFailScale: CGFloat = 0.3 let sheetHeightRatio: CGFloat = 0.43 From 776983d9932cf5b5661dfc3f8f4d90d80e9d2121 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 02:01:05 +0900 Subject: [PATCH 4/7] =?UTF-8?q?perf:=20=EB=AD=89=EC=B9=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=20=EC=A7=84=EC=9E=85=20=EC=8B=9C?= =?UTF-8?q?=20=EC=8B=9C=ED=8A=B8=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=ED=94=84?= =?UTF-8?q?=EB=A6=AC=ED=8E=98=EC=B9=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 배경/카라비너 데이터 로드 후 ImagePrefetcher로 썸네일을 Nuke 캐시에 미리 로드하여 최초 시트 오픈 시 렉 감소 (약간 체감 미미하긴 한듯) --- .../BundleCreateView+Initialization.swift | 12 +++++++++++- .../Edit/BundleEditView+Initialization.swift | 17 ++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Initialization.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Initialization.swift index 590e56ec..25d41f2f 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Initialization.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Initialization.swift @@ -6,14 +6,24 @@ // import SwiftUI +import Nuke // MARK: - 데이터 초기화 extension BundleCreateView { /// 초기 데이터 로딩 func initializeData() async { - // 사용자가 소유한 배경과 카라비너 데이터를 가져옴 await loadUserOwnedItems() + // 시트 이미지 프리페칭 (백그라운드에서 Nuke 캐시에 미리 로드) + prefetchSheetImages() + } + + /// 배경/카라비너 썸네일을 Nuke 캐시에 미리 로드 + private func prefetchSheetImages() { + let bgURLs = bundleVM.backgroundViewData.compactMap { URL(string: $0.background.backgroundImage) } + let cbURLs = bundleVM.carabinerViewData.compactMap { URL(string: $0.carabiner.carabinerImage[0]) } + let prefetcher = ImagePrefetcher() + prefetcher.startPrefetching(with: bgURLs + cbURLs) } /// 화면이 다시 나타날 때 데이터 새로고침 diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+Initialization.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+Initialization.swift index d724f702..4436b0ac 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+Initialization.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView+Initialization.swift @@ -7,15 +7,26 @@ import SwiftUI import FirebaseFirestore +import Nuke extension BundleEditView { func initializeData() async { resetSceneState() - + await loadUserKeyring() - + await loadBackgroundAndCarabiner() - + + // 시트 이미지 프리페칭 (백그라운드에서 Nuke 캐시에 미리 로드) + prefetchSheetImages() + } + + /// 배경/카라비너 썸네일을 Nuke 캐시에 미리 로드 + private func prefetchSheetImages() { + let bgURLs = bundleVM.backgroundViewData.compactMap { URL(string: $0.background.backgroundImage) } + let cbURLs = bundleVM.carabinerViewData.compactMap { URL(string: $0.carabiner.carabinerImage[0]) } + let prefetcher = ImagePrefetcher() + prefetcher.startPrefetching(with: bgURLs + cbURLs) } func resetSceneState() { From b9cad46fd4e1ed28b9e697e03c02628b44bbc271 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 02:23:48 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=ED=82=A4=EB=A7=81=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=AC=B4=EB=A3=8C=ED=85=9C=EC=9D=80=20=EB=B3=B4?= =?UTF-8?q?=EC=9C=A0=20=ED=83=9C=EA=B7=B8=EA=B0=80=20=EB=9C=A8=EB=A9=B4=20?= =?UTF-8?q?=EC=95=88=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workshop/Views/Keyring/WorkshopTemplateSelectSheet.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopTemplateSelectSheet.swift b/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopTemplateSelectSheet.swift index d84e7b25..a2c8bb83 100644 --- a/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopTemplateSelectSheet.swift +++ b/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopTemplateSelectSheet.swift @@ -192,7 +192,7 @@ struct WorkshopTemplateSelectSheet: View { Spacer() - // 보유 뱃지 (오른쪽 상단) - 보유 또는 무료일 때 + // 보유 뱃지 (오른쪽 상단) - 유료 + 보유일 때만 Text("보유") .typography(.suit13M) .foregroundStyle(.white100) @@ -202,7 +202,7 @@ struct WorkshopTemplateSelectSheet: View { RoundedRectangle(cornerRadius: 20) .fill(.black60) ) - .opacity(isOwned || template.isFree ? 1 : 0) + .opacity(isOwned && !template.isFree ? 1 : 0) } .padding(.top, 6) .padding(.horizontal, 7) From 69f40c5be6d44c8e276373b7416a929a34c7768c Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 02:24:03 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=EB=B2=88=EB=93=A4=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF=EC=9D=B4=20=EB=B0=B1=EA=B7=B8=EB=9D=BC=EC=9A=B4?= =?UTF-8?q?=EB=93=9C=EA=B0=80=20=EC=9E=88=EB=8A=94=20=EC=9E=98=EB=AA=BB?= =?UTF-8?q?=EB=90=9C=20=EC=BA=90=EC=8B=9C=EB=A1=9C=20=EB=93=A4=EC=96=B4?= =?UTF-8?q?=EA=B0=80=EB=8D=98=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Bundle/ViewModels/BundleViewModel.swift | 1 + .../Create/BundleCreateView+Capture.swift | 19 ++++++++++++++++++- .../Views/Create/BundleNameInputView.swift | 3 ++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/ViewModels/BundleViewModel.swift b/Keychy/Keychy/Presentation/Bundle/ViewModels/BundleViewModel.swift index d4e4b139..bb28ed62 100644 --- a/Keychy/Keychy/Presentation/Bundle/ViewModels/BundleViewModel.swift +++ b/Keychy/Keychy/Presentation/Bundle/ViewModels/BundleViewModel.swift @@ -60,6 +60,7 @@ class BundleViewModel { // MARK: - 뭉치 캡쳐 이미지 var bundleCapturedImage: Data? + var bundleWidgetImage: Data? // MARK: - 현재 선택된 뭉치 diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Capture.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Capture.swift index 96a97c3e..50d5d352 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Capture.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleCreateView+Capture.swift @@ -104,7 +104,7 @@ extension BundleCreateView { carabinerFrontURL = nil } - // 씬 캡처 + // 1. 배경 포함 캡처 (앱용) if let pngData = await MultiKeyringCaptureScene.captureBundleImage( keyringDataList: keyringDataList, backgroundImageURL: background.backgroundImage, @@ -121,6 +121,23 @@ extension BundleCreateView { } } + // 2. 배경 없이 캡처 (위젯용 - 투명 여백 제거) + let widgetData = await MultiKeyringCaptureScene.captureBundleImage( + keyringDataList: keyringDataList, + backgroundImageURL: nil, + carabinerBackImageURL: carabinerBackURL, + carabinerFrontImageURL: carabinerFrontURL, + carabinerType: carabinerType, + carabinerId: carabiner.id ?? "", + carabinerX: carabiner.carabinerX, + carabinerY: carabiner.carabinerY, + carabinerWidth: carabiner.carabinerWidth, + trimTransparentEdges: true + ) + await MainActor.run { + bundleVM.bundleWidgetImage = widgetData + } + // 캡처 완료 후 다음 화면으로 이동 await MainActor.run { isCapturing = false diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleNameInputView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleNameInputView.swift index 44ad6ce1..8673c272 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleNameInputView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Create/BundleNameInputView.swift @@ -223,7 +223,8 @@ extension BundleNameInputView { // Firebase 저장 성공 후 ViewModel의 이미지를 캐시에 저장 bundleVM.saveBundleImageToCache( bundleId: bundleId, - bundleName: bundleNameToSave + bundleName: bundleNameToSave, + widgetImageData: bundleVM.bundleWidgetImage ) isUploading = false From c93c81c0abd6fdd4b34886618d1253d71bb51c76 Mon Sep 17 00:00:00 2001 From: giljihun Date: Sun, 15 Feb 2026 02:33:06 +0900 Subject: [PATCH 7/7] fix: qs 6.7.0 - 6.14.1 qs's arrayLimit bypass in comma parsing allows denial of service - https://github.com/advisories/GHSA-w7fw-mjwx-w883 --- functions/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/package-lock.json b/functions/package-lock.json index 72632e41..ff04a6a3 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -5910,9 +5910,9 @@ "peer": true }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0"