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
8 changes: 8 additions & 0 deletions Keychy/Keychy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@
4C77753F2EB1343600981C3E /* IntroViewModel+Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77753A2EB1343600981C3E /* IntroViewModel+Login.swift */; };
4C7775402EB1343600981C3E /* IntroViewModel+Signup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77753C2EB1343600981C3E /* IntroViewModel+Signup.swift */; };
4C7775412EB1343600981C3E /* IntroViewModel+NicknameSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77753B2EB1343600981C3E /* IntroViewModel+NicknameSetup.swift */; };
4C7A9EC72F2B0567008B520C /* KeyringPackageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7A9EC62F2B0567008B520C /* KeyringPackageManager.swift */; };
4C7A9EC92F2B0586008B520C /* KeyringPackageCompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7A9EC82F2B0586008B520C /* KeyringPackageCompleteView.swift */; };
4C8426602ED3585A0050B6FE /* gulimche-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4C84265F2ED3585A0050B6FE /* gulimche-Regular.ttf */; };
4C8426642ED375840050B6FE /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8426632ED375840050B6FE /* ColorPalette.swift */; };
4C84A1602EB134BD008FFE57 /* ProfileSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A596832EAFEAA20003D712 /* ProfileSetupView.swift */; };
Expand Down Expand Up @@ -687,6 +689,8 @@
4C77753A2EB1343600981C3E /* IntroViewModel+Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntroViewModel+Login.swift"; sourceTree = "<group>"; };
4C77753B2EB1343600981C3E /* IntroViewModel+NicknameSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntroViewModel+NicknameSetup.swift"; sourceTree = "<group>"; };
4C77753C2EB1343600981C3E /* IntroViewModel+Signup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntroViewModel+Signup.swift"; sourceTree = "<group>"; };
4C7A9EC62F2B0567008B520C /* KeyringPackageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringPackageManager.swift; sourceTree = "<group>"; };
4C7A9EC82F2B0586008B520C /* KeyringPackageCompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringPackageCompleteView.swift; sourceTree = "<group>"; };
4C84265F2ED3585A0050B6FE /* gulimche-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = text; path = "gulimche-Regular.ttf"; sourceTree = "<group>"; };
4C8426632ED375840050B6FE /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = "<group>"; };
4C86A6112F25C0B10023AA2D /* WorkshopBundleGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopBundleGridView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -938,6 +942,7 @@
C645AEA22EB1B8FC004BFE69 /* DataInitializer.swift */,
C6830F032EB8A4000059379A /* WorkshopDataManager.swift */,
4C07024A2ECF10760026D6DC /* EffectSyncManager.swift */,
4C7A9EC62F2B0567008B520C /* KeyringPackageManager.swift */,
);
path = Firebase;
sourceTree = "<group>";
Expand Down Expand Up @@ -1207,6 +1212,7 @@
4C47333F2F1FA388005D2376 /* KeyringCompleteView+ReviewCheck.swift */,
4C4733402F1FA388005D2376 /* KeyringCompleteView+SaveImage.swift */,
4C4733412F1FA388005D2376 /* KeyringCompleteView+VideoGen.swift */,
4C7A9EC82F2B0586008B520C /* KeyringPackageCompleteView.swift */,
4C4733422F1FA388005D2376 /* KeyringCustomizingView.swift */,
4C4733432F1FA388005D2376 /* KeyringCustomizingView+Cart.swift */,
4C4733442F1FA388005D2376 /* KeyringCustomizingView+Purchase.swift */,
Expand Down Expand Up @@ -2671,6 +2677,7 @@
3828F54B2EC4D0C500F1B040 /* CollectionView+Handlers.swift in Sources */,
4CF2A96A2F0B94EA00BA9FDA /* View+PullToRefresh.swift in Sources */,
4C86A61F2F29E52D0023AA2D /* PurchaseHistoryView.swift in Sources */,
4C7A9EC72F2B0567008B520C /* KeyringPackageManager.swift in Sources */,
38C147BB2EB13B2F00A8E511 /* CircleGlassButton.swift in Sources */,
AA6298542EC39065001576C0 /* BundleCreateView.swift in Sources */,
38C147C72EB1F57F00A8E511 /* StorageManager.swift in Sources */,
Expand Down Expand Up @@ -2711,6 +2718,7 @@
4CC8D01F2EF0447100317467 /* ChangeNameViewModel.swift in Sources */,
4CC8D0202EF0447100317467 /* MyPageViewModel.swift in Sources */,
4C4734072F226B81005D2376 /* WorkshopMakeMenu.swift in Sources */,
4C7A9EC92F2B0586008B520C /* KeyringPackageCompleteView.swift in Sources */,
38C147C52EB1F16A00A8E511 /* CollectionViewModel+LoadData.swift in Sources */,
C6C35F3A2ED2A3C2009642F4 /* FestivalViewModel.swift in Sources */,
AAEB46AF2EC1D648002B13E5 /* BundleNameEditView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ extension KeyringSceneView {
Group {
if let scene {
SpriteView(scene: scene, options: [.allowsTransparency])
.ignoresSafeArea()
.contentShape(Rectangle())
.frame(maxWidth: .infinity)
}
Expand Down
177 changes: 177 additions & 0 deletions Keychy/Keychy/Core/Firebase/KeyringPackageManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// KeyringPackageManager.swift
// Keychy
//
// 키링 선물 포장 로직 (Collection, Workshop 공용)
//

import Foundation
import FirebaseFirestore

/// 키링 선물 포장 관리자
enum KeyringPackageManager {

// MARK: - 키링 포장하기

/// 키링을 선물용으로 포장합니다.
/// - Parameters:
/// - uid: 사용자 ID
/// - keyringDocumentId: 키링 Firestore Document ID
/// - completion: 완료 콜백 (성공 여부, PostOffice ID, Share Link)
static func packageKeyring(
uid: String,
keyringDocumentId: String,
completion: @escaping (Bool, String?, String?) -> Void
) {
let db = Firestore.firestore()

// 1. Keyring 상태 업데이트 (isPackaged = true)
db.collection("Keyring")
.document(keyringDocumentId)
.updateData(["isPackaged": true]) { error in
if let error = error {
print("[Package] Keyring 상태 업데이트 실패: \(error.localizedDescription)")
completion(false, nil, nil)
return
}

print("[Package] Keyring 상태 업데이트 완료")

// 2. PostOffice 문서 생성
createPostOffice(
db: db,
uid: uid,
keyringDocumentId: keyringDocumentId,
completion: completion
)
}
}

// MARK: - Private Helpers

/// PostOffice 문서 생성
private static func createPostOffice(
db: Firestore,
uid: String,
keyringDocumentId: String,
completion: @escaping (Bool, String?, String?) -> Void
) {
let postOfficeRef = db.collection("PostOffice").document()
let postOfficeId = postOfficeRef.documentID

// 중복 체크 (희귀 케이스)
postOfficeRef.getDocument { checkSnapshot, checkError in
if checkSnapshot?.exists == true {
print("[Package] PostOffice ID 중복 발견 - 재시도")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
packageKeyring(uid: uid, keyringDocumentId: keyringDocumentId, completion: completion)
}
return
}

// 공유 링크 생성
guard let shareLink = DeepLinkManager.createShareLink(postOfficeId: postOfficeId) else {
print("[Package] 공유 링크 생성 실패")
completion(false, nil, nil)
return
}

let shareLinkString = shareLink.absoluteString
print("[Package] 공유 링크 생성: \(shareLinkString)")

// PostOffice 문서 데이터
let postOfficeData: [String: Any] = [
"type": "receive",
"senderId": uid,
"keyringId": keyringDocumentId,
"shareLink": shareLinkString,
"createdAt": Timestamp(date: Date())
]

// 문서 생성
postOfficeRef.setData(postOfficeData) { error in
if let error = error {
print("[Package] PostOffice 문서 생성 실패: \(error.localizedDescription)")
completion(false, nil, nil)
return
}

print("[Package] PostOffice 문서 생성 완료: \(postOfficeId)")

// Bundle에서 키링 제거
removeKeyringFromBundles(db: db, uid: uid, keyringDocumentId: keyringDocumentId) { _ in
completion(true, postOfficeId, shareLinkString)
}
}
}
}

/// Bundle에서 키링 제거
private static func removeKeyringFromBundles(
db: Firestore,
uid: String,
keyringDocumentId: String,
completion: @escaping (Bool) -> Void
) {
db.collection("KeyringBundle")
.whereField("userId", isEqualTo: uid)
.getDocuments { snapshot, error in
if error != nil {
completion(false)
return
}

guard let documents = snapshot?.documents, !documents.isEmpty else {
print("[Package] Bundle 없음")
completion(true)
return
}

let batch = db.batch()
var affectedBundleIds: [String] = []

for document in documents {
guard var keyrings = document.data()["keyrings"] as? [String] else {
continue
}

var needsUpdate = false

for (index, keyring) in keyrings.enumerated() {
if keyring == keyringDocumentId {
keyrings[index] = "none"
needsUpdate = true
}
}

if needsUpdate {
let bundleRef = db.collection("KeyringBundle").document(document.documentID)
batch.updateData(["keyrings": keyrings], forDocument: bundleRef)
affectedBundleIds.append(document.documentID)
}
}

if affectedBundleIds.isEmpty {
completion(true)
return
}

batch.commit { error in
if let error = error {
print("[Package] Bundle 업데이트 실패: \(error.localizedDescription)")
completion(false)
return
}

print("[Package] \(affectedBundleIds.count)개 Bundle에서 키링 제거 완료")

// Bundle 캡처 캐시 삭제
for bundleId in affectedBundleIds {
BundleImageCache.shared.delete(for: bundleId)
}

completion(true)
}
}
}
}
3 changes: 3 additions & 0 deletions Keychy/Keychy/Core/Navigation/Routes/WorkshopRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ enum WorkshopRoute: Hashable, BundleRoute {
case speechBubbleInfoInput
case speechBubbleComplete

// MARK: - 선물 포장 완료
case packageComplete(keyringDocumentId: String, postOfficeId: String, templateId: String, shareLink: String)

/// template.id 문자열을 WorkshopRoute로 변환
static func from(string: String) -> WorkshopRoute? {
switch string {
Expand Down
14 changes: 14 additions & 0 deletions Keychy/Keychy/Core/Navigation/TabBarManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import UIKit

/// 탭바 표시/숨김 전역 관리
enum TabBarManager {
/// 탭 인덱스
enum TabIndex: Int {
case home = 0
case workshop = 1
case collection = 2
case festival = 3
}

/// 탭바 숨기기
static func hide() {
guard let tabBarController = findTabBarController() else { return }
Expand All @@ -24,6 +32,12 @@ enum TabBarManager {
}
}

/// 특정 탭으로 전환
static func switchTo(_ tab: TabIndex) {
guard let tabBarController = findTabBarController() else { return }
tabBarController.selectedIndex = tab.rawValue
}

/// TabBarController 찾기
private static func findTabBarController() -> UITabBarController? {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ private class KeyringAdapter: KeyringViewModelProtocol {
func isInCache(particleId: String) -> Bool { false }
func downloadSound(_ sound: Sound) async { }
func downloadParticle(_ particle: Particle) async { }
var savedKeyringDocumentId: String?
var packagedPostOfficeId: String?
var packagedShareLink: String?

func resetCustomizingData() { }
func resetInfoData() { }
func resetAll() { }
Expand Down
Loading