Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions Keychy/Keychy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
AA0000022F17000100000001 /* BundleSwitchPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000012F17000100000001 /* BundleSwitchPopup.swift */; };
38173D082EB8AD3900E36F7E /* CollectionViewModel+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38173D072EB8AD3900E36F7E /* CollectionViewModel+Tags.swift */; };
38173D0A2EB8AD7900E36F7E /* CategoryTabBarWithLongPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38173D092EB8AD7900E36F7E /* CategoryTabBarWithLongPress.swift */; };
38173D0C2EB8AD8800E36F7E /* CategoryContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38173D0B2EB8AD8800E36F7E /* CategoryContextMenu.swift */; };
Expand Down Expand Up @@ -293,7 +294,6 @@
4CEBB14E2EFAA52F00CF53E2 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB14B2EFAA52F00CF53E2 /* DeepLinkManager.swift */; };
4CEBB1552EFACFA900CF53E2 /* HomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB1512EFACFA900CF53E2 /* HomeTab.swift */; };
4CEBB1572EFACFA900CF53E2 /* CollectionTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB14F2EFACFA900CF53E2 /* CollectionTab.swift */; };
4CEBB1582EFACFA900CF53E2 /* FestivalTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB1502EFACFA900CF53E2 /* FestivalTab.swift */; };
4CEBB1592EFACFA900CF53E2 /* WorkshopTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB1532EFACFA900CF53E2 /* WorkshopTab.swift */; };
4CEBB15F2EFAD3D600CF53E2 /* MainTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB15E2EFAD3D600CF53E2 /* MainTabViewModel.swift */; };
4CEBB1612EFAD3F800CF53E2 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEBB1602EFAD3F800CF53E2 /* MainTabView.swift */; };
Expand Down Expand Up @@ -321,7 +321,6 @@
4CEC62232EAE08DA0099ECEE /* KeyringRingComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FE2EAE08DA0099ECEE /* KeyringRingComponent.swift */; };
4CEC62252EAE08DA0099ECEE /* WorkshopRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62092EAE08DA0099ECEE /* WorkshopRoute.swift */; };
4CEC62262EAE08DA0099ECEE /* Radius.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62132EAE08DA0099ECEE /* Radius.swift */; };
4CEC62282EAE08DA0099ECEE /* FestivalRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC620A2EAE08DA0099ECEE /* FestivalRoute.swift */; };
4CEC62292EAE08DA0099ECEE /* KeyringCellScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61F32EAE08DA0099ECEE /* KeyringCellScene.swift */; };
4CEC622A2EAE08DA0099ECEE /* Font+Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62102EAE08DA0099ECEE /* Font+Custom.swift */; };
4CEC622B2EAE08DA0099ECEE /* Font+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62112EAE08DA0099ECEE /* Font+Styles.swift */; };
Expand Down Expand Up @@ -464,6 +463,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
AA0000012F17000100000001 /* BundleSwitchPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleSwitchPopup.swift; sourceTree = "<group>"; };
38173D072EB8AD3900E36F7E /* CollectionViewModel+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewModel+Tags.swift"; sourceTree = "<group>"; };
38173D092EB8AD7900E36F7E /* CategoryTabBarWithLongPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryTabBarWithLongPress.swift; sourceTree = "<group>"; };
38173D0B2EB8AD8800E36F7E /* CategoryContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryContextMenu.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -742,7 +742,6 @@
4CEBB14A2EFAA52F00CF53E2 /* DeepLinkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkHandler.swift; sourceTree = "<group>"; };
4CEBB14B2EFAA52F00CF53E2 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = "<group>"; };
4CEBB14F2EFACFA900CF53E2 /* CollectionTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTab.swift; sourceTree = "<group>"; };
4CEBB1502EFACFA900CF53E2 /* FestivalTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FestivalTab.swift; sourceTree = "<group>"; };
4CEBB1512EFACFA900CF53E2 /* HomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTab.swift; sourceTree = "<group>"; };
4CEBB1532EFACFA900CF53E2 /* WorkshopTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopTab.swift; sourceTree = "<group>"; };
4CEBB15E2EFAD3D600CF53E2 /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -776,7 +775,6 @@
4CEC62072EAE08DA0099ECEE /* HomeRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRoute.swift; sourceTree = "<group>"; };
4CEC62082EAE08DA0099ECEE /* CollectionRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionRoute.swift; sourceTree = "<group>"; };
4CEC62092EAE08DA0099ECEE /* WorkshopRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopRoute.swift; sourceTree = "<group>"; };
4CEC620A2EAE08DA0099ECEE /* FestivalRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FestivalRoute.swift; sourceTree = "<group>"; };
4CEC620C2EAE08DA0099ECEE /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = "<group>"; };
4CEC62102EAE08DA0099ECEE /* Font+Custom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Custom.swift"; sourceTree = "<group>"; };
4CEC62112EAE08DA0099ECEE /* Font+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Styles.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -913,6 +911,7 @@
38173D0D2EB902FD00E36F7E /* Popup */ = {
isa = PBXGroup;
children = (
AA0000012F17000100000001 /* BundleSwitchPopup.swift */,
38173D0E2EB9083900E36F7E /* DeletePopup.swift */,
38173D102EB90CCE00E36F7E /* TagInputPopup.swift */,
3822DDBD2EBAAC80003125BE /* PurchasePopup.swift */,
Expand Down Expand Up @@ -1661,7 +1660,6 @@
4CEBB1512EFACFA900CF53E2 /* HomeTab.swift */,
4CEBB1532EFACFA900CF53E2 /* WorkshopTab.swift */,
4CEBB14F2EFACFA900CF53E2 /* CollectionTab.swift */,
4CEBB1502EFACFA900CF53E2 /* FestivalTab.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -1866,7 +1864,6 @@
4CEC62072EAE08DA0099ECEE /* HomeRoute.swift */,
4CEC62082EAE08DA0099ECEE /* CollectionRoute.swift */,
4CEC62092EAE08DA0099ECEE /* WorkshopRoute.swift */,
4CEC620A2EAE08DA0099ECEE /* FestivalRoute.swift */,
AA390CE42ECC60A700D87EEC /* BundleRoute.swift */,
);
path = Routes;
Expand Down Expand Up @@ -2496,7 +2493,6 @@
AA8C9B8C2F0F349500A352D2 /* BundleDetailView+Alert.swift in Sources */,
4CEC62252EAE08DA0099ECEE /* WorkshopRoute.swift in Sources */,
4CEC62262EAE08DA0099ECEE /* Radius.swift in Sources */,
4CEC62282EAE08DA0099ECEE /* FestivalRoute.swift in Sources */,
4CEC62292EAE08DA0099ECEE /* KeyringCellScene.swift in Sources */,
AA2146BB2F161D0C0048D40E /* BundleEditView+Initialization.swift in Sources */,
3828F5492EC4CCE400F1B040 /* CollectionView+SearchMode.swift in Sources */,
Expand All @@ -2508,6 +2504,7 @@
AA69DD262F14C60000C0A41C /* BundleViewModel+Edit.swift in Sources */,
4CC3D3C52EC701610009D376 /* IntroViewModel+Bundle.swift in Sources */,
38173D0F2EB9083900E36F7E /* DeletePopup.swift in Sources */,
AA0000022F17000100000001 /* BundleSwitchPopup.swift in Sources */,
AA390CE52ECC60A700D87EEC /* BundleRoute.swift in Sources */,
38C3C27E2EC08794003C5DE1 /* PopupManager.swift in Sources */,
C6C4028D2EB2741D006B58DF /* Sound.swift in Sources */,
Expand Down Expand Up @@ -2703,7 +2700,6 @@
38173D0A2EB8AD7900E36F7E /* CategoryTabBarWithLongPress.swift in Sources */,
4CEBB1552EFACFA900CF53E2 /* HomeTab.swift in Sources */,
4CEBB1572EFACFA900CF53E2 /* CollectionTab.swift in Sources */,
4CEBB1582EFACFA900CF53E2 /* FestivalTab.swift in Sources */,
4CEBB1592EFACFA900CF53E2 /* WorkshopTab.swift in Sources */,
38F832CD2EC90DEF00D3A248 /* WidgetOnboardingStepView+Helpers.swift in Sources */,
38A22A9D2EC27AC400B4C7C5 /* PackagedKeyringView+SaveImage.swift in Sources */,
Expand Down
51 changes: 30 additions & 21 deletions Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,35 +181,45 @@ class MultiKeyringScene: SKScene {
}

private func beginLoading() {
// 카라비너 이미지와 키링들을 로드
Task {
async let carabinerBackTask: Void = {
if let carabinerBackURL = await carabinerBackImageURL {
if carabinerBackURL != "none" {
await setupCarabinerBackImageAsync(url: carabinerBackURL)
}
}
await MainActor.run { self.carabinerBackReady = true }
}()
Task { [weak self] in
guard let self else { return }
guard !self.isCleaningUp else { return }

async let carabinerFrontTask: Void = {
if let carabinerFrontURL = await carabinerFrontImageURL {
await setupCarabinerFrontImageAsync(url: carabinerFrontURL)
}
await MainActor.run { self.carabinerFrontReady = true }
}()
// 카라비너 이미지 병렬 로드
async let backLoaded: Void = self.loadCarabinerBack()
async let frontLoaded: Void = self.loadCarabinerFront()

// 카라비너 이미지 로드 병렬 실행
await carabinerBackTask
await carabinerFrontTask
await backLoaded
await frontLoaded

// 키링 설정 (카라비너 준비 후)
await MainActor.run {
await MainActor.run { [weak self] in
guard let self, !self.isCleaningUp else { return }
self.setupKeyringsIfNeeded()
}
}
}

/// 카라비너 뒷면 이미지 로드
private func loadCarabinerBack() async {
if let url = carabinerBackImageURL, url != "none" {
await setupCarabinerBackImageAsync(url: url)
}
await MainActor.run { [weak self] in
self?.carabinerBackReady = true
}
}

/// 카라비너 앞면 이미지 로드
private func loadCarabinerFront() async {
if let url = carabinerFrontImageURL {
await setupCarabinerFrontImageAsync(url: url)
}
await MainActor.run { [weak self] in
self?.carabinerFrontReady = true
}
}

// MARK: - Shadow Helper

/// 노드에 수직 그림자 추가 (z축 위에서 내려오는 광원)
Expand Down Expand Up @@ -328,7 +338,6 @@ class MultiKeyringScene: SKScene {

private func setupKeyringsIfNeeded() {
guard !didStartKeyringSetup else { return }
// 카라비너가 준비된 뒤에만 시작
guard carabinerBackReady && carabinerFrontReady else { return }
didStartKeyringSetup = true
setupKeyrings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,11 @@ struct MultiKeyringSceneView: View {
particleEffectsView
}
.onAppear {
// 씬이 없을 때 초기 설정 (키링이 없어도 배경과 카라비너는 표시)
if scene == nil {
loadBackgroundImage()
setupScene()
}
}
.onChange(of: keyringDataList) { oldValue, newValue in
// 데이터가 실제로 변경된 경우에만 씬 재생성
if oldValue != newValue {
loadBackgroundImage()
setupScene()
}
}
.onChange(of: backgroundImageURL) { _, _ in
loadBackgroundImage()
}
Expand Down Expand Up @@ -172,20 +164,18 @@ extension MultiKeyringSceneView {

/// 씬 초기화 및 설정
private func setupScene() {
// 기존 씬이 있다면 명시적으로 정리
if scene != nil {
cleanupScene()
}

// 키링 준비 카운터 리셋

visibleKeyringCount = 0

let newScene = MultiKeyringScene(
keyringDataList: keyringDataList,
ringType: ringType,
chainType: chainType,
backgroundColor: .clear, // 배경은 투명하게
backgroundImageURL: nil, // 배경은 SwiftUI에서 처리
backgroundColor: .clear,
backgroundImageURL: nil,
carabinerBackImageURL: carabinerBackImageURL,
carabinerFrontImageURL: carabinerFrontImageURL,
carabinerX: carabinerX,
Expand Down
140 changes: 140 additions & 0 deletions Keychy/Keychy/Core/Components/View/Popup/BundleSwitchPopup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// BundleSwitchPopup.swift
// Keychy
//
// Created by Claude on 2/3/26.
//

import SwiftUI

/// 홈 화면에서 뭉치를 변경할 수 있는 드롭다운 팝업
struct BundleSwitchPopup: View {
let bundles: [KeyringBundle]
let currentBundle: KeyringBundle?
let onSelect: (KeyringBundle) -> Void

/// 대표 뭉치 (isMain == true)
private var mainBundle: KeyringBundle? {
bundles.first(where: { $0.isMain })
}

/// 선택 가능한 뭉치들 (대표 제외)
private var selectableBundles: [KeyringBundle] {
bundles.filter { !$0.isMain }
}

var body: some View {
VStack(alignment: .leading, spacing: 0) {
// 대표 섹션
if let main = mainBundle {
mainSection(bundle: main)
}

// 구분선
Rectangle()
.fill(.gray100)
.frame(height: 1)
.padding(.horizontal, 18)

// 선택 섹션
if !selectableBundles.isEmpty {
selectSection(bundles: selectableBundles)
}
}
.frame(width: 196)
.padding(.vertical, 5)
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 34))
}

// MARK: - 대표 섹션

private func mainSection(bundle: KeyringBundle) -> some View {
VStack(alignment: .leading, spacing: 0) {
Text("대표")
.typography(.suit13M)
.foregroundStyle(.gray200)
.padding(.horizontal, 20)
.padding(.top, 10)
.padding(.bottom, 8)

Button {
onSelect(bundle)
} label: {
HStack {
Text(bundle.name)
.typography(.suit16M)
.foregroundStyle(currentBundle?.documentId == bundle.documentId ? .gray600 : .gray400)
Spacer()
}
.padding(.horizontal, 20)
.padding(.bottom, 12)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}

// MARK: - 선택 섹션

private func selectSection(bundles: [KeyringBundle]) -> some View {
VStack(alignment: .leading, spacing: 0) {
Text("선택")
.typography(.suit13M)
.foregroundStyle(.gray200)
.padding(.horizontal, 20)
.padding(.top, 12)
.padding(.bottom, 10)

VStack(alignment: .leading, spacing: 25) {
ForEach(bundles, id: \.documentId) { bundle in
Button {
onSelect(bundle)
} label: {
HStack {
Text(bundle.name)
.typography(.suit16M)
.foregroundStyle(currentBundle?.documentId == bundle.documentId ? .gray600 : .gray400)
Spacer()
}
.padding(.horizontal, 20)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
.padding(.bottom, 10)
}
}
}

// MARK: - 드롭다운 버튼
struct BundleSwitchButton: View {
let bundleName: String
let isExpanded: Bool
let isEnabled: Bool
let onTap: () -> Void

var body: some View {
Button(action: onTap) {
HStack(spacing: 12) {
Text(bundleName)
.typography(.nanum24EB)
.foregroundStyle(.black100)

if isEnabled {
Image(systemName: "chevron.down")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(.black)
.rotationEffect(.degrees(isExpanded ? 180 : 0))
.offset(y: 0.5)
.frame(width: 24, height: 24)
.background(Color.white)
.clipShape(Circle())
.overlay(Circle().stroke(Color(#colorLiteral(red: 0.8861967921, green: 0.8861967921, blue: 0.8861967921, alpha: 1)), lineWidth: 1))
}
}
}
.buttonStyle(.plain)
.disabled(!isEnabled)
}
}
17 changes: 0 additions & 17 deletions Keychy/Keychy/Core/Navigation/Routes/FestivalRoute.swift

This file was deleted.

Loading