diff --git a/Keychy/Keychy.xcodeproj/project.pbxproj b/Keychy/Keychy.xcodeproj/project.pbxproj index 42181b0ca..a7d14d6b5 100644 --- a/Keychy/Keychy.xcodeproj/project.pbxproj +++ b/Keychy/Keychy.xcodeproj/project.pbxproj @@ -32,9 +32,6 @@ 385425BE2EB2989400A06C02 /* CollectionCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425BD2EB2989400A06C02 /* CollectionCellView.swift */; }; 385425C02EB2AE7800A06C02 /* CollectionViewModel+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425BF2EB2AE7800A06C02 /* CollectionViewModel+Sort.swift */; }; 385425C32EB2C35E00A06C02 /* WidgetSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425C22EB2C35E00A06C02 /* WidgetSettingView.swift */; }; - 385425C82EB3C3F300A06C02 /* KeyringDetailSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425C72EB3C3F300A06C02 /* KeyringDetailSceneView.swift */; }; - 385425CA2EB3C79A00A06C02 /* KeyringDetailScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425C92EB3C79A00A06C02 /* KeyringDetailScene.swift */; }; - 385425CC2EB3C7CC00A06C02 /* KeyringDetailScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385425CB2EB3C7CC00A06C02 /* KeyringDetailScene+Setup.swift */; }; 386102422F0F7C8C0045C529 /* KeyringReceiveViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 386102412F0F7C8C0045C529 /* KeyringReceiveViewModel.swift */; }; 386102442F0F7C980045C529 /* KeyringCollectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 386102432F0F7C980045C529 /* KeyringCollectViewModel.swift */; }; 386102462F10F9CE0045C529 /* KeyringReceiveView+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 386102452F10F9CE0045C529 /* KeyringReceiveView+Alerts.swift */; }; @@ -112,6 +109,26 @@ 4C25259E2F3037D6003CC5AD /* BundleImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B5707E2EC206CD0049F969 /* BundleImageCache.swift */; }; 4C2525DA2F35B2A7003CC5AD /* BundleSheetFilterBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525D92F35B2A7003CC5AD /* BundleSheetFilterBar.swift */; }; 4C2525DD2F39E38C003CC5AD /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 4C2525DC2F39E38C003CC5AD /* FirebaseAppCheck */; }; + 4C2525FB2F3B27B3003CC5AD /* MultiKeyringScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525F52F3B27B3003CC5AD /* MultiKeyringScene.swift */; }; + 4C2525FC2F3B27B3003CC5AD /* MultiKeyringSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525F72F3B27B3003CC5AD /* MultiKeyringSceneView.swift */; }; + 4C2525FF2F3B27B3003CC5AD /* MultiKeyringCaptureScene+Capture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525F42F3B27B3003CC5AD /* MultiKeyringCaptureScene+Capture.swift */; }; + 4C2526002F3B27B3003CC5AD /* MultiKeyringCaptureScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525F32F3B27B3003CC5AD /* MultiKeyringCaptureScene.swift */; }; + 4C2526022F3B27B3003CC5AD /* KeyringDetailSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525ED2F3B27B3003CC5AD /* KeyringDetailSceneView.swift */; }; + 4C2526032F3B27B3003CC5AD /* BundleRingComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525F12F3B27B3003CC5AD /* BundleRingComponent.swift */; }; + 4C2526052F3B27B3003CC5AD /* KeyringChainComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525DF2F3B27B3003CC5AD /* KeyringChainComponent.swift */; }; + 4C2526072F3B27B3003CC5AD /* KeyringBodyComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525DE2F3B27B3003CC5AD /* KeyringBodyComponent.swift */; }; + 4C25260A2F3B27B3003CC5AD /* KeyringRingComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525E02F3B27B3003CC5AD /* KeyringRingComponent.swift */; }; + 4C25260C2F3B27B3003CC5AD /* KeyringSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2525EE2F3B27B3003CC5AD /* KeyringSceneView.swift */; }; + 4C25261B2F3B290A003CC5AD /* KeyringDetailScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526132F3B290A003CC5AD /* KeyringDetailScene+Setup.swift */; }; + 4C25261C2F3B290A003CC5AD /* KeyringScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526152F3B290A003CC5AD /* KeyringScene.swift */; }; + 4C25261D2F3B290A003CC5AD /* KeyringCellScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C25260E2F3B290A003CC5AD /* KeyringCellScene.swift */; }; + 4C25261E2F3B290A003CC5AD /* KeyringScene+Effects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526162F3B290A003CC5AD /* KeyringScene+Effects.swift */; }; + 4C25261F2F3B290A003CC5AD /* KeyringCellScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526102F3B290A003CC5AD /* KeyringCellScene+Setup.swift */; }; + 4C2526202F3B290A003CC5AD /* KeyringDetailScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526122F3B290A003CC5AD /* KeyringDetailScene.swift */; }; + 4C2526212F3B290A003CC5AD /* KeyringScene+Touch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526192F3B290A003CC5AD /* KeyringScene+Touch.swift */; }; + 4C2526222F3B290A003CC5AD /* KeyringScene+Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526182F3B290A003CC5AD /* KeyringScene+Swipe.swift */; }; + 4C2526232F3B290A003CC5AD /* KeyringScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2526172F3B290A003CC5AD /* KeyringScene+Setup.swift */; }; + 4C2526242F3B290A003CC5AD /* KeyringCellScene+Capture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C25260F2F3B290A003CC5AD /* KeyringCellScene+Capture.swift */; }; 4C3687F72EBFA87800C64E75 /* Pretendard-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4C3687F62EBFA87800C64E75 /* Pretendard-Medium.ttf */; }; 4C3687FA2EBFC0FB00C64E75 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3687F82EBFC0FB00C64E75 /* NotificationManager.swift */; }; 4C3687FC2EC05E6800C64E75 /* AccountAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3687FB2EC05E6800C64E75 /* AccountAlert.swift */; }; @@ -314,27 +331,16 @@ 4CEC61EA2EAE08C00099ECEE /* RingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61E02EAE08C00099ECEE /* RingType.swift */; }; 4CEC61EC2EAE08C00099ECEE /* BodyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61E22EAE08C00099ECEE /* BodyType.swift */; }; 4CEC61F12EAE08C40099ECEE /* KeychyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61EE2EAE08C40099ECEE /* KeychyApp.swift */; }; - 4CEC621A2EAE08DA0099ECEE /* KeyringScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61F92EAE08DA0099ECEE /* KeyringScene.swift */; }; - 4CEC621B2EAE08DA0099ECEE /* KeyringScene+Touch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FC2EAE08DA0099ECEE /* KeyringScene+Touch.swift */; }; 4CEC621C2EAE08DA0099ECEE /* BackgroundCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61F62EAE08DA0099ECEE /* BackgroundCell.swift */; }; 4CEC621D2EAE08DA0099ECEE /* HomeRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62072EAE08DA0099ECEE /* HomeRoute.swift */; }; - 4CEC621E2EAE08DA0099ECEE /* KeyringChainComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FF2EAE08DA0099ECEE /* KeyringChainComponent.swift */; }; - 4CEC621F2EAE08DA0099ECEE /* KeyringBodyComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62002EAE08DA0099ECEE /* KeyringBodyComponent.swift */; }; 4CEC62202EAE08DA0099ECEE /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62172EAE08DA0099ECEE /* View+Extension.swift */; }; 4CEC62212EAE08DA0099ECEE /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62162EAE08DA0099ECEE /* UIImage+Extension.swift */; }; 4CEC62222EAE08DA0099ECEE /* CollectionRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62082EAE08DA0099ECEE /* CollectionRoute.swift */; }; - 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 */; }; - 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 */; }; - 4CEC622C2EAE08DA0099ECEE /* KeyringSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62012EAE08DA0099ECEE /* KeyringSceneView.swift */; }; - 4CEC622D2EAE08DA0099ECEE /* KeyringScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FB2EAE08DA0099ECEE /* KeyringScene+Setup.swift */; }; - 4CEC622E2EAE08DA0099ECEE /* KeyringCellScene+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61F42EAE08DA0099ECEE /* KeyringCellScene+Setup.swift */; }; 4CEC622F2EAE08DA0099ECEE /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC620C2EAE08DA0099ECEE /* NavigationRouter.swift */; }; - 4CEC62302EAE08DA0099ECEE /* KeyringScene+Effects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FA2EAE08DA0099ECEE /* KeyringScene+Effects.swift */; }; - 4CEC62312EAE08DA0099ECEE /* KeyringScene+Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61FD2EAE08DA0099ECEE /* KeyringScene+Swipe.swift */; }; 4CEC62332EAE08DA0099ECEE /* BundleGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC61F52EAE08DA0099ECEE /* BundleGridItem.swift */; }; 4CEC62342EAE08DA0099ECEE /* Spacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62142EAE08DA0099ECEE /* Spacing.swift */; }; 4CEC626C2EAE08DF0099ECEE /* FestivalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC62442EAE08DF0099ECEE /* FestivalView.swift */; }; @@ -355,7 +361,6 @@ AA0000022F17000100000001 /* BundleSwitchPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000012F17000100000001 /* BundleSwitchPopup.swift */; }; AA0219DE2EB1C041006EF269 /* BundleNameInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0219DD2EB1C041006EF269 /* BundleNameInputView.swift */; }; AA0A54B72EC053E4007B5413 /* CarabinerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0A54B62EC053E4007B5413 /* CarabinerType.swift */; }; - AA0A54B92EC05C41007B5413 /* BundleRingComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0A54B82EC05C41007B5413 /* BundleRingComponent.swift */; }; AA2146AF2F15D0160048D40E /* BundleEditView+Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2146AE2F15D0160048D40E /* BundleEditView+Purchase.swift */; }; AA2146B12F15D43C0048D40E /* BundleEditView+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2146B02F15D43C0048D40E /* BundleEditView+Alert.swift */; }; AA2146B52F15D8490048D40E /* KeyringCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2146B42F15D8490048D40E /* KeyringCell.swift */; }; @@ -406,15 +411,12 @@ C665DDF02EAF08D000CE4495 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C665DDEF2EAF08D000CE4495 /* PurchaseManager.swift */; }; C67B755F2ECD526A00D6E3FA /* Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67B755D2ECD526A00D6E3FA /* Frame.swift */; }; C6830F042EB8A4000059379A /* WorkshopDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6830F032EB8A4000059379A /* WorkshopDataManager.swift */; }; - C6830F172EBB08380059379A /* MultiKeyringScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6830F152EBB08380059379A /* MultiKeyringScene.swift */; }; - C6830F182EBB08380059379A /* MultiKeyringSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6830F162EBB08380059379A /* MultiKeyringSceneView.swift */; }; C68931CE2EB7B94B00C5F083 /* EffectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C68931CC2EB7B94B00C5F083 /* EffectManager.swift */; }; C6B56F0B2EBC43AC0049F969 /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F0A2EBC43AC0049F969 /* StoreProduct.swift */; }; C6B56F112EBD96110049F969 /* CoinChargeView+Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F102EBD96110049F969 /* CoinChargeView+Purchase.swift */; }; C6B56F132EBD962E0049F969 /* CurrentItemsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F122EBD962E0049F969 /* CurrentItemsCard.swift */; }; C6B56F202EBF72130049F969 /* ItemPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F1F2EBF72130049F969 /* ItemPurchaseManager.swift */; }; C6B56F232EC0341B0049F969 /* KeyringImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F212EC0341B0049F969 /* KeyringImageCache.swift */; }; - C6B56F2B2EC039D30049F969 /* KeyringCellScene+Capture.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F2A2EC039D30049F969 /* KeyringCellScene+Capture.swift */; }; C6B56F2D2EC03A000049F969 /* CachedImagesDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F2C2EC03A000049F969 /* CachedImagesDebugView.swift */; }; C6B56F342EC061BB0049F969 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6B56F332EC061BB0049F969 /* WidgetKit.framework */; }; C6B56F362EC061BB0049F969 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6B56F352EC061BB0049F969 /* SwiftUI.framework */; }; @@ -422,9 +424,7 @@ C6B56F602EC08BCF0049F969 /* WidgetKeyring.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F5F2EC08BCF0049F969 /* WidgetKeyring.swift */; }; C6B56F612EC08CCE0049F969 /* KeyringImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F212EC0341B0049F969 /* KeyringImageCache.swift */; }; C6B56F702EC08ED40049F969 /* WidgetKeyring.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B56F5F2EC08BCF0049F969 /* WidgetKeyring.swift */; }; - C6B5707B2EC2036C0049F969 /* MultiKeyringCaptureScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B5707A2EC2036C0049F969 /* MultiKeyringCaptureScene.swift */; }; C6B5707F2EC206CD0049F969 /* BundleImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B5707E2EC206CD0049F969 /* BundleImageCache.swift */; }; - C6B571062EC2337C0049F969 /* MultiKeyringCaptureScene+Capture.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B571052EC2337C0049F969 /* MultiKeyringCaptureScene+Capture.swift */; }; C6C35EF72ED17228009642F4 /* RealStoreKit2.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C6C35EF62ED17228009642F4 /* RealStoreKit2.storekit */; }; C6C35EF82ED17228009642F4 /* RealStoreKit2.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C6C35EF62ED17228009642F4 /* RealStoreKit2.storekit */; }; C6C35F3A2ED2A3C2009642F4 /* FestivalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C35F372ED2A3C2009642F4 /* FestivalViewModel.swift */; }; @@ -506,9 +506,6 @@ 385425BD2EB2989400A06C02 /* CollectionCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionCellView.swift; sourceTree = ""; }; 385425BF2EB2AE7800A06C02 /* CollectionViewModel+Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewModel+Sort.swift"; sourceTree = ""; }; 385425C22EB2C35E00A06C02 /* WidgetSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetSettingView.swift; sourceTree = ""; }; - 385425C72EB3C3F300A06C02 /* KeyringDetailSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringDetailSceneView.swift; sourceTree = ""; }; - 385425C92EB3C79A00A06C02 /* KeyringDetailScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringDetailScene.swift; sourceTree = ""; }; - 385425CB2EB3C7CC00A06C02 /* KeyringDetailScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringDetailScene+Setup.swift"; sourceTree = ""; }; 386102412F0F7C8C0045C529 /* KeyringReceiveViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringReceiveViewModel.swift; sourceTree = ""; }; 386102432F0F7C980045C529 /* KeyringCollectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringCollectViewModel.swift; sourceTree = ""; }; 386102452F10F9CE0045C529 /* KeyringReceiveView+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringReceiveView+Alerts.swift"; sourceTree = ""; }; @@ -577,6 +574,26 @@ 4C07024A2ECF10760026D6DC /* EffectSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectSyncManager.swift; sourceTree = ""; }; 4C25259B2F303745003CC5AD /* WidgetBundleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBundleModel.swift; sourceTree = ""; }; 4C2525D92F35B2A7003CC5AD /* BundleSheetFilterBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleSheetFilterBar.swift; sourceTree = ""; }; + 4C2525DE2F3B27B3003CC5AD /* KeyringBodyComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringBodyComponent.swift; sourceTree = ""; }; + 4C2525DF2F3B27B3003CC5AD /* KeyringChainComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringChainComponent.swift; sourceTree = ""; }; + 4C2525E02F3B27B3003CC5AD /* KeyringRingComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringRingComponent.swift; sourceTree = ""; }; + 4C2525ED2F3B27B3003CC5AD /* KeyringDetailSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringDetailSceneView.swift; sourceTree = ""; }; + 4C2525EE2F3B27B3003CC5AD /* KeyringSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringSceneView.swift; sourceTree = ""; }; + 4C2525F12F3B27B3003CC5AD /* BundleRingComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleRingComponent.swift; sourceTree = ""; }; + 4C2525F32F3B27B3003CC5AD /* MultiKeyringCaptureScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringCaptureScene.swift; sourceTree = ""; }; + 4C2525F42F3B27B3003CC5AD /* MultiKeyringCaptureScene+Capture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MultiKeyringCaptureScene+Capture.swift"; sourceTree = ""; }; + 4C2525F52F3B27B3003CC5AD /* MultiKeyringScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringScene.swift; sourceTree = ""; }; + 4C2525F72F3B27B3003CC5AD /* MultiKeyringSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringSceneView.swift; sourceTree = ""; }; + 4C25260E2F3B290A003CC5AD /* KeyringCellScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringCellScene.swift; sourceTree = ""; }; + 4C25260F2F3B290A003CC5AD /* KeyringCellScene+Capture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringCellScene+Capture.swift"; sourceTree = ""; }; + 4C2526102F3B290A003CC5AD /* KeyringCellScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringCellScene+Setup.swift"; sourceTree = ""; }; + 4C2526122F3B290A003CC5AD /* KeyringDetailScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringDetailScene.swift; sourceTree = ""; }; + 4C2526132F3B290A003CC5AD /* KeyringDetailScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringDetailScene+Setup.swift"; sourceTree = ""; }; + 4C2526152F3B290A003CC5AD /* KeyringScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringScene.swift; sourceTree = ""; }; + 4C2526162F3B290A003CC5AD /* KeyringScene+Effects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Effects.swift"; sourceTree = ""; }; + 4C2526172F3B290A003CC5AD /* KeyringScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Setup.swift"; sourceTree = ""; }; + 4C2526182F3B290A003CC5AD /* KeyringScene+Swipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Swipe.swift"; sourceTree = ""; }; + 4C2526192F3B290A003CC5AD /* KeyringScene+Touch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Touch.swift"; sourceTree = ""; }; 4C3687F62EBFA87800C64E75 /* Pretendard-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Medium.ttf"; sourceTree = ""; }; 4C3687F82EBFC0FB00C64E75 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 4C3687FB2EC05E6800C64E75 /* AccountAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAlert.swift; sourceTree = ""; }; @@ -780,19 +797,8 @@ 4CEC61E12EAE08C00099ECEE /* ChainType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainType.swift; sourceTree = ""; }; 4CEC61E22EAE08C00099ECEE /* BodyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyType.swift; sourceTree = ""; }; 4CEC61EE2EAE08C40099ECEE /* KeychyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychyApp.swift; sourceTree = ""; }; - 4CEC61F32EAE08DA0099ECEE /* KeyringCellScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringCellScene.swift; sourceTree = ""; }; - 4CEC61F42EAE08DA0099ECEE /* KeyringCellScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringCellScene+Setup.swift"; sourceTree = ""; }; 4CEC61F52EAE08DA0099ECEE /* BundleGridItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleGridItem.swift; sourceTree = ""; }; 4CEC61F62EAE08DA0099ECEE /* BackgroundCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundCell.swift; sourceTree = ""; }; - 4CEC61F92EAE08DA0099ECEE /* KeyringScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringScene.swift; sourceTree = ""; }; - 4CEC61FA2EAE08DA0099ECEE /* KeyringScene+Effects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Effects.swift"; sourceTree = ""; }; - 4CEC61FB2EAE08DA0099ECEE /* KeyringScene+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Setup.swift"; sourceTree = ""; }; - 4CEC61FC2EAE08DA0099ECEE /* KeyringScene+Touch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Touch.swift"; sourceTree = ""; }; - 4CEC61FD2EAE08DA0099ECEE /* KeyringScene+Swipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringScene+Swipe.swift"; sourceTree = ""; }; - 4CEC61FE2EAE08DA0099ECEE /* KeyringRingComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringRingComponent.swift; sourceTree = ""; }; - 4CEC61FF2EAE08DA0099ECEE /* KeyringChainComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringChainComponent.swift; sourceTree = ""; }; - 4CEC62002EAE08DA0099ECEE /* KeyringBodyComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringBodyComponent.swift; sourceTree = ""; }; - 4CEC62012EAE08DA0099ECEE /* KeyringSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringSceneView.swift; sourceTree = ""; }; 4CEC62072EAE08DA0099ECEE /* HomeRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRoute.swift; sourceTree = ""; }; 4CEC62082EAE08DA0099ECEE /* CollectionRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionRoute.swift; sourceTree = ""; }; 4CEC62092EAE08DA0099ECEE /* WorkshopRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopRoute.swift; sourceTree = ""; }; @@ -820,7 +826,6 @@ AA0000012F17000100000001 /* BundleSwitchPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleSwitchPopup.swift; sourceTree = ""; }; AA0219DD2EB1C041006EF269 /* BundleNameInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleNameInputView.swift; sourceTree = ""; }; AA0A54B62EC053E4007B5413 /* CarabinerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarabinerType.swift; sourceTree = ""; }; - AA0A54B82EC05C41007B5413 /* BundleRingComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleRingComponent.swift; sourceTree = ""; }; AA2146AE2F15D0160048D40E /* BundleEditView+Purchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleEditView+Purchase.swift"; sourceTree = ""; }; AA2146B02F15D43C0048D40E /* BundleEditView+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleEditView+Alert.swift"; sourceTree = ""; }; AA2146B42F15D8490048D40E /* KeyringCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringCell.swift; sourceTree = ""; }; @@ -871,15 +876,12 @@ C665DDEF2EAF08D000CE4495 /* PurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; C67B755D2ECD526A00D6E3FA /* Frame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Frame.swift; sourceTree = ""; }; C6830F032EB8A4000059379A /* WorkshopDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopDataManager.swift; sourceTree = ""; }; - C6830F152EBB08380059379A /* MultiKeyringScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringScene.swift; sourceTree = ""; }; - C6830F162EBB08380059379A /* MultiKeyringSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringSceneView.swift; sourceTree = ""; }; C68931CC2EB7B94B00C5F083 /* EffectManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectManager.swift; sourceTree = ""; }; C6B56F0A2EBC43AC0049F969 /* StoreProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProduct.swift; sourceTree = ""; }; C6B56F102EBD96110049F969 /* CoinChargeView+Purchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinChargeView+Purchase.swift"; sourceTree = ""; }; C6B56F122EBD962E0049F969 /* CurrentItemsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentItemsCard.swift; sourceTree = ""; }; C6B56F1F2EBF72130049F969 /* ItemPurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPurchaseManager.swift; sourceTree = ""; }; C6B56F212EC0341B0049F969 /* KeyringImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringImageCache.swift; sourceTree = ""; }; - C6B56F2A2EC039D30049F969 /* KeyringCellScene+Capture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyringCellScene+Capture.swift"; sourceTree = ""; }; C6B56F2C2EC03A000049F969 /* CachedImagesDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedImagesDebugView.swift; sourceTree = ""; }; C6B56F322EC061BB0049F969 /* WidgetKeychyExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetKeychyExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C6B56F332EC061BB0049F969 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -887,9 +889,7 @@ C6B56F4D2EC0681C0049F969 /* KeychyRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KeychyRelease.entitlements; sourceTree = ""; }; C6B56F5C2EC06B310049F969 /* WidgetKeychyExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetKeychyExtension.entitlements; sourceTree = ""; }; C6B56F5F2EC08BCF0049F969 /* WidgetKeyring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetKeyring.swift; sourceTree = ""; }; - C6B5707A2EC2036C0049F969 /* MultiKeyringCaptureScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiKeyringCaptureScene.swift; sourceTree = ""; }; C6B5707E2EC206CD0049F969 /* BundleImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleImageCache.swift; sourceTree = ""; }; - C6B571052EC2337C0049F969 /* MultiKeyringCaptureScene+Capture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MultiKeyringCaptureScene+Capture.swift"; sourceTree = ""; }; C6C35EF62ED17228009642F4 /* RealStoreKit2.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = RealStoreKit2.storekit; sourceTree = ""; }; C6C35F372ED2A3C2009642F4 /* FestivalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FestivalViewModel.swift; sourceTree = ""; }; C6C35F3B2ED2A7BD009642F4 /* Showcase25BoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Showcase25BoardView.swift; sourceTree = ""; }; @@ -1115,6 +1115,112 @@ path = Keyring; sourceTree = ""; }; + 4C2525E12F3B27B3003CC5AD /* Components */ = { + isa = PBXGroup; + children = ( + 4C2525DE2F3B27B3003CC5AD /* KeyringBodyComponent.swift */, + 4C2525DF2F3B27B3003CC5AD /* KeyringChainComponent.swift */, + 4C2525E02F3B27B3003CC5AD /* KeyringRingComponent.swift */, + ); + path = Components; + sourceTree = ""; + }; + 4C2525EC2F3B27B3003CC5AD /* Scene */ = { + isa = PBXGroup; + children = ( + 4C25261A2F3B290A003CC5AD /* KeyringScene */, + 4C2526142F3B290A003CC5AD /* DetailScene */, + 4C2526112F3B290A003CC5AD /* CellScene */, + ); + path = Scene; + sourceTree = ""; + }; + 4C2525EF2F3B27B3003CC5AD /* View */ = { + isa = PBXGroup; + children = ( + 4C2525ED2F3B27B3003CC5AD /* KeyringDetailSceneView.swift */, + 4C2525EE2F3B27B3003CC5AD /* KeyringSceneView.swift */, + ); + path = View; + sourceTree = ""; + }; + 4C2525F02F3B27B3003CC5AD /* Keyring */ = { + isa = PBXGroup; + children = ( + 4C2525EC2F3B27B3003CC5AD /* Scene */, + 4C2525EF2F3B27B3003CC5AD /* View */, + 4C2525E12F3B27B3003CC5AD /* Components */, + ); + path = Keyring; + sourceTree = ""; + }; + 4C2525F22F3B27B3003CC5AD /* Components */ = { + isa = PBXGroup; + children = ( + 4C2525F12F3B27B3003CC5AD /* BundleRingComponent.swift */, + ); + path = Components; + sourceTree = ""; + }; + 4C2525F62F3B27B3003CC5AD /* Scene */ = { + isa = PBXGroup; + children = ( + 4C2525F52F3B27B3003CC5AD /* MultiKeyringScene.swift */, + 4C2525F32F3B27B3003CC5AD /* MultiKeyringCaptureScene.swift */, + 4C2525F42F3B27B3003CC5AD /* MultiKeyringCaptureScene+Capture.swift */, + ); + path = Scene; + sourceTree = ""; + }; + 4C2525F82F3B27B3003CC5AD /* View */ = { + isa = PBXGroup; + children = ( + 4C2525F72F3B27B3003CC5AD /* MultiKeyringSceneView.swift */, + ); + path = View; + sourceTree = ""; + }; + 4C2525F92F3B27B3003CC5AD /* KeyringBundle */ = { + isa = PBXGroup; + children = ( + 4C2525F62F3B27B3003CC5AD /* Scene */, + 4C2525F82F3B27B3003CC5AD /* View */, + 4C2525F22F3B27B3003CC5AD /* Components */, + ); + path = KeyringBundle; + sourceTree = ""; + }; + 4C2526112F3B290A003CC5AD /* CellScene */ = { + isa = PBXGroup; + children = ( + 4C25260E2F3B290A003CC5AD /* KeyringCellScene.swift */, + 4C25260F2F3B290A003CC5AD /* KeyringCellScene+Capture.swift */, + 4C2526102F3B290A003CC5AD /* KeyringCellScene+Setup.swift */, + ); + path = CellScene; + sourceTree = ""; + }; + 4C2526142F3B290A003CC5AD /* DetailScene */ = { + isa = PBXGroup; + children = ( + 4C2526122F3B290A003CC5AD /* KeyringDetailScene.swift */, + 4C2526132F3B290A003CC5AD /* KeyringDetailScene+Setup.swift */, + ); + path = DetailScene; + sourceTree = ""; + }; + 4C25261A2F3B290A003CC5AD /* KeyringScene */ = { + isa = PBXGroup; + children = ( + 4C2526152F3B290A003CC5AD /* KeyringScene.swift */, + 4C2526162F3B290A003CC5AD /* KeyringScene+Effects.swift */, + 4C2526172F3B290A003CC5AD /* KeyringScene+Setup.swift */, + 4C2526182F3B290A003CC5AD /* KeyringScene+Swipe.swift */, + 4C2526192F3B290A003CC5AD /* KeyringScene+Touch.swift */, + ); + path = KeyringScene; + sourceTree = ""; + }; 4C3687F52EBFA86B00C64E75 /* pretendard */ = { isa = PBXGroup; children = ( @@ -1852,29 +1958,11 @@ 4C8426632ED375840050B6FE /* ColorPalette.swift */, 4CF2A9652F0B8C5800BA9FDA /* PullToRefreshIndicator.swift */, 4CF2A9672F0B91F300BA9FDA /* AnimatedGIFView.swift */, + C645AE9E2EB1055C004BFE69 /* CategoryTabBar.swift */, ); path = View; sourceTree = ""; }; - 4CEC62022EAE08DA0099ECEE /* Keyring */ = { - isa = PBXGroup; - children = ( - 4CEC61F92EAE08DA0099ECEE /* KeyringScene.swift */, - 4CEC61FA2EAE08DA0099ECEE /* KeyringScene+Effects.swift */, - 4CEC61FB2EAE08DA0099ECEE /* KeyringScene+Setup.swift */, - 4CEC61FC2EAE08DA0099ECEE /* KeyringScene+Touch.swift */, - 4CEC61FD2EAE08DA0099ECEE /* KeyringScene+Swipe.swift */, - 4CEC61FE2EAE08DA0099ECEE /* KeyringRingComponent.swift */, - 4CEC61FF2EAE08DA0099ECEE /* KeyringChainComponent.swift */, - 4CEC62002EAE08DA0099ECEE /* KeyringBodyComponent.swift */, - 4CEC62012EAE08DA0099ECEE /* KeyringSceneView.swift */, - 385425C92EB3C79A00A06C02 /* KeyringDetailScene.swift */, - 385425CB2EB3C7CC00A06C02 /* KeyringDetailScene+Setup.swift */, - 385425C72EB3C3F300A06C02 /* KeyringDetailSceneView.swift */, - ); - path = Keyring; - sourceTree = ""; - }; 4CEC62052EAE08DA0099ECEE /* Effects */ = { isa = PBXGroup; children = ( @@ -1889,13 +1977,7 @@ 4CEBB16C2EFCFB9C00CF53E2 /* NetworkError */, 4CA9C6A52EC9D11600CA546B /* Navigation */, 4C65303C2EBA5FF3000F8154 /* Alerts */, - AADD2AB52EB3AF520051478E /* KeyringBundle */, - 4CEC61F32EAE08DA0099ECEE /* KeyringCellScene.swift */, - 4CEC61F42EAE08DA0099ECEE /* KeyringCellScene+Setup.swift */, - C6B56F2A2EC039D30049F969 /* KeyringCellScene+Capture.swift */, - C645AE9E2EB1055C004BFE69 /* CategoryTabBar.swift */, 4CEC61F82EAE08DA0099ECEE /* View */, - 4CEC62022EAE08DA0099ECEE /* Keyring */, 4CEC62052EAE08DA0099ECEE /* Effects */, ); path = Components; @@ -1969,6 +2051,8 @@ 4CEC62192EAE08DA0099ECEE /* Core */ = { isa = PBXGroup; children = ( + 4C2525F02F3B27B3003CC5AD /* Keyring */, + 4C2525F92F3B27B3003CC5AD /* KeyringBundle */, 4CDCADC12F14CC5A00C01972 /* Video */, 4CF2A96F2F0FB2E100BA9FDA /* Update */, 4CEBB1662EFBDCCD00CF53E2 /* Network */, @@ -2209,18 +2293,6 @@ path = Festival; sourceTree = ""; }; - AADD2AB52EB3AF520051478E /* KeyringBundle */ = { - isa = PBXGroup; - children = ( - C6B571052EC2337C0049F969 /* MultiKeyringCaptureScene+Capture.swift */, - C6B5707A2EC2036C0049F969 /* MultiKeyringCaptureScene.swift */, - C6830F152EBB08380059379A /* MultiKeyringScene.swift */, - C6830F162EBB08380059379A /* MultiKeyringSceneView.swift */, - AA0A54B82EC05C41007B5413 /* BundleRingComponent.swift */, - ); - path = KeyringBundle; - sourceTree = ""; - }; AD292C97DAFD4C18A064840D /* Edit */ = { isa = PBXGroup; children = ( @@ -2515,7 +2587,6 @@ 4CEBB1482EFA93D000CF53E2 /* RootView.swift in Sources */, 4CEBB1492EFA93D000CF53E2 /* AppDelegate.swift in Sources */, 4C6622152EAE70BF001760B5 /* Gradient+Keychy.swift in Sources */, - C6B56F2B2EC039D30049F969 /* KeyringCellScene+Capture.swift in Sources */, 386B17542ECCE8EC00CCCC23 /* CollectionViewModel+Distribution.swift in Sources */, 3822DDC62EBBE252003125BE /* LackPopup.swift in Sources */, 4CEBB16F2EFCFC5600CF53E2 /* NoInternetView.swift in Sources */, @@ -2542,9 +2613,6 @@ 4C65303E2EBA6042000F8154 /* ImageSaveAlert.swift in Sources */, 4CC8D00D2EF030E300317467 /* CollectionViewModel.swift in Sources */, AA8C9B982F10E6D500A352D2 /* BundleViewModel.swift in Sources */, - C6B5707B2EC2036C0049F969 /* MultiKeyringCaptureScene.swift in Sources */, - 4CEC621A2EAE08DA0099ECEE /* KeyringScene.swift in Sources */, - 4CEC621B2EAE08DA0099ECEE /* KeyringScene+Touch.swift in Sources */, 38C3C2882EC1D761003C5DE1 /* CollectionKeyringDetailView+Menu.swift in Sources */, 4CEC621C2EAE08DA0099ECEE /* BackgroundCell.swift in Sources */, 3828F5472EC4CCDC00F1B040 /* CollectionView+NormalMode.swift in Sources */, @@ -2555,20 +2623,15 @@ 38A5967A2EAFA94E0003D712 /* IntroView.swift in Sources */, 4C004FB52F18D98C00D9063E /* ReviewManager.swift in Sources */, 38283A7F2EBD3E8400BE45A5 /* PackageCompleteView.swift in Sources */, - 4CEC621E2EAE08DA0099ECEE /* KeyringChainComponent.swift in Sources */, - 4CEC621F2EAE08DA0099ECEE /* KeyringBodyComponent.swift in Sources */, 4CEC62202EAE08DA0099ECEE /* View+Extension.swift in Sources */, 4C4733E52F20FE34005D2376 /* WorkshopRecentTemplate.swift in Sources */, 38173D0C2EB8AD8800E36F7E /* CategoryContextMenu.swift in Sources */, - C6830F172EBB08380059379A /* MultiKeyringScene.swift in Sources */, - C6830F182EBB08380059379A /* MultiKeyringSceneView.swift in Sources */, 385425BE2EB2989400A06C02 /* CollectionCellView.swift in Sources */, 4CEC62212EAE08DA0099ECEE /* UIImage+Extension.swift in Sources */, 4CEC62222EAE08DA0099ECEE /* CollectionRoute.swift in Sources */, AA3909462EC9F29500D87EEC /* UIApplication+Extension.swift in Sources */, 4CC8D0252EF11CD200317467 /* HomeViewModel.swift in Sources */, C68931CE2EB7B94B00C5F083 /* EffectManager.swift in Sources */, - 4CEC62232EAE08DA0099ECEE /* KeyringRingComponent.swift in Sources */, C6B56F202EBF72130049F969 /* ItemPurchaseManager.swift in Sources */, 38C3C28C2EC1E4B4003C5DE1 /* CollectionKeyringDetailView+Alerts.swift in Sources */, 4CAF11AB2EBF6058004CB08C /* CollectionKeyringDetailView+SaveImage.swift in Sources */, @@ -2578,7 +2641,6 @@ AA8C9B8C2F0F349500A352D2 /* BundleDetailView+Alert.swift in Sources */, 4CEC62252EAE08DA0099ECEE /* WorkshopRoute.swift in Sources */, 4CEC62262EAE08DA0099ECEE /* Radius.swift in Sources */, - 4CEC62292EAE08DA0099ECEE /* KeyringCellScene.swift in Sources */, AA2146BB2F161D0C0048D40E /* BundleEditView+Initialization.swift in Sources */, 3828F5492EC4CCE400F1B040 /* CollectionView+SearchMode.swift in Sources */, AA9B2E8B2EB001B70004D31C /* Carabiner.swift in Sources */, @@ -2604,7 +2666,6 @@ 4CEC622A2EAE08DA0099ECEE /* Font+Custom.swift in Sources */, AA2146B72F15E5B60048D40E /* BundleEditView+SelectSheet.swift in Sources */, 389080172ED3F05D00D7A49F /* FestivalKeyringDetailView.swift in Sources */, - 385425C82EB3C3F300A06C02 /* KeyringDetailSceneView.swift in Sources */, 38A22A7F2EC2238800B4C7C5 /* CollectionKeyringPackageView.swift in Sources */, 4CC3D3C82EC8A47E0009D376 /* OutlineText.swift in Sources */, 385425C32EB2C35E00A06C02 /* WidgetSettingView.swift in Sources */, @@ -2716,7 +2777,6 @@ 4C6530442EBA8077000F8154 /* PurchaseSuccessAlert.swift in Sources */, 4C6622322EAF6AF7001760B5 /* Typography.swift in Sources */, 4CA9C6F92ECBA5EF00CA546B /* NotificationItemView.swift in Sources */, - C6B571062EC2337C0049F969 /* MultiKeyringCaptureScene+Capture.swift in Sources */, 3822DDC22EBBC712003125BE /* KeyringEditView.swift in Sources */, 4CF2A9662F0B8C5800BA9FDA /* PullToRefreshIndicator.swift in Sources */, 4C4734002F22615D005D2376 /* WorkshopItemCard.swift in Sources */, @@ -2735,6 +2795,16 @@ 4C4733F92F226143005D2376 /* TemplateActionButton.swift in Sources */, 4C4733232F1FA2AB005D2376 /* WorkshopView.swift in Sources */, 4C4733272F1FA2AB005D2376 /* WorkshopBundleBanner.swift in Sources */, + 4C2525FB2F3B27B3003CC5AD /* MultiKeyringScene.swift in Sources */, + 4C2525FC2F3B27B3003CC5AD /* MultiKeyringSceneView.swift in Sources */, + 4C2525FF2F3B27B3003CC5AD /* MultiKeyringCaptureScene+Capture.swift in Sources */, + 4C2526002F3B27B3003CC5AD /* MultiKeyringCaptureScene.swift in Sources */, + 4C2526022F3B27B3003CC5AD /* KeyringDetailSceneView.swift in Sources */, + 4C2526032F3B27B3003CC5AD /* BundleRingComponent.swift in Sources */, + 4C2526052F3B27B3003CC5AD /* KeyringChainComponent.swift in Sources */, + 4C2526072F3B27B3003CC5AD /* KeyringBodyComponent.swift in Sources */, + 4C25260A2F3B27B3003CC5AD /* KeyringRingComponent.swift in Sources */, + 4C25260C2F3B27B3003CC5AD /* KeyringSceneView.swift in Sources */, 4C4733282F1FA2AB005D2376 /* WorkshopTemplatesView.swift in Sources */, 4C47332D2F1FA2AB005D2376 /* WorkshopViewModel.swift in Sources */, 4C86A6142F25C0BA0023AA2D /* WorkshopKeyringGridView.swift in Sources */, @@ -2791,7 +2861,6 @@ AA9115082EB126A60026E9BC /* CarabinerCell.swift in Sources */, 38283A832EBF554E00BE45A5 /* KeyringReceiveView.swift in Sources */, 4CEC627F2EAE08DF0099ECEE /* CollectionView.swift in Sources */, - AA0A54B92EC05C41007B5413 /* BundleRingComponent.swift in Sources */, C6C361422ED44EE4009642F4 /* Showcase25BoardView+Sheet.swift in Sources */, 4C2525DA2F35B2A7003CC5AD /* BundleSheetFilterBar.swift in Sources */, BC00020F2F35F00200000003 /* BundleSearchBar.swift in Sources */, @@ -2804,7 +2873,6 @@ 386102442F0F7C980045C529 /* KeyringCollectViewModel.swift in Sources */, 4C65306E2EBCF157000F8154 /* TermsView.swift in Sources */, AA91150A2EB1B7930026E9BC /* AddKeyringButton.swift in Sources */, - 4CEC622C2EAE08DA0099ECEE /* KeyringSceneView.swift in Sources */, C645AEA32EB1B8FC004BFE69 /* DataInitializer.swift in Sources */, 4CA9C6F72ECBA45200CA546B /* KeychyNotification.swift in Sources */, 4CF2A9682F0B91F300BA9FDA /* AnimatedGIFView.swift in Sources */, @@ -2818,19 +2886,14 @@ AAEB46AF2EC1D648002B13E5 /* BundleNameEditView.swift in Sources */, AA9B2E892EB001AA0004D31C /* Background.swift in Sources */, C6C35F3C2ED2A7BD009642F4 /* Showcase25BoardView.swift in Sources */, - 4CEC622D2EAE08DA0099ECEE /* KeyringScene+Setup.swift in Sources */, 38D17A512EBBF88C00F52A88 /* CollectionViewModel+Edit.swift in Sources */, - 4CEC622E2EAE08DA0099ECEE /* KeyringCellScene+Setup.swift in Sources */, C6B5707F2EC206CD0049F969 /* BundleImageCache.swift in Sources */, C645AE9F2EB1055C004BFE69 /* CategoryTabBar.swift in Sources */, 386102422F0F7C8C0045C529 /* KeyringReceiveViewModel.swift in Sources */, 4CEC622F2EAE08DA0099ECEE /* NavigationRouter.swift in Sources */, 4C6530502EBB2A90000F8154 /* LoadingAlert.swift in Sources */, - 4CEC62302EAE08DA0099ECEE /* KeyringScene+Effects.swift in Sources */, - 385425CC2EB3C7CC00A06C02 /* KeyringDetailScene+Setup.swift in Sources */, 4C6530722EBCFD10000F8154 /* BangmarkAlert.swift in Sources */, 389080192ED3F32700D7A49F /* FestivalKeyringDetailView+Sheet.swift in Sources */, - 4CEC62312EAE08DA0099ECEE /* KeyringScene+Swipe.swift in Sources */, 4C004F912F164D5500D9063E /* TabBarManager.swift in Sources */, C665DDF02EAF08D000CE4495 /* PurchaseManager.swift in Sources */, 4CEC62332EAE08DA0099ECEE /* BundleGridItem.swift in Sources */, @@ -2844,6 +2907,16 @@ AA3908F82EC8BF0400D87EEC /* BundleDetailView+SaveImage.swift in Sources */, BC4CMPLT2F3B123400000001 /* BundleCompleteView.swift in Sources */, BC5CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift in Sources */, + 4C25261B2F3B290A003CC5AD /* KeyringDetailScene+Setup.swift in Sources */, + 4C25261C2F3B290A003CC5AD /* KeyringScene.swift in Sources */, + 4C25261D2F3B290A003CC5AD /* KeyringCellScene.swift in Sources */, + 4C25261E2F3B290A003CC5AD /* KeyringScene+Effects.swift in Sources */, + 4C25261F2F3B290A003CC5AD /* KeyringCellScene+Setup.swift in Sources */, + 4C2526202F3B290A003CC5AD /* KeyringDetailScene.swift in Sources */, + 4C2526212F3B290A003CC5AD /* KeyringScene+Touch.swift in Sources */, + 4C2526222F3B290A003CC5AD /* KeyringScene+Swipe.swift in Sources */, + 4C2526232F3B290A003CC5AD /* KeyringScene+Setup.swift in Sources */, + 4C2526242F3B290A003CC5AD /* KeyringCellScene+Capture.swift in Sources */, BC6CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift in Sources */, 4C4733EB2F22553F005D2376 /* WorkshopView+StickyHeader.swift in Sources */, 4C4733F72F225A2C005D2376 /* WorkshopItemDetailView.swift in Sources */, @@ -2853,7 +2926,6 @@ AA0219DE2EB1C041006EF269 /* BundleNameInputView.swift in Sources */, 4CEC62342EAE08DA0099ECEE /* Spacing.swift in Sources */, 38A596732EAFA8D20003D712 /* KeychyUser.swift in Sources */, - 385425CA2EB3C79A00A06C02 /* KeyringDetailScene.swift in Sources */, 4CA9C6D92ECB7B2800CA546B /* TextFilter.swift in Sources */, 3890801B2ED3F3BB00D7A49F /* FestivalKeyringDetailView+Alerts.swift in Sources */, C6C35F412ED2B6E6009642F4 /* ShowcaseFestivalKeyring.swift in Sources */, diff --git a/Keychy/Keychy/Core/CarabinerScene+Interaction.swift b/Keychy/Keychy/Core/CarabinerScene+Interaction.swift deleted file mode 100644 index 769a8c328..000000000 --- a/Keychy/Keychy/Core/CarabinerScene+Interaction.swift +++ /dev/null @@ -1,254 +0,0 @@ -// -// CarabinerScene+Interaction.swift -// KeytschPrototype -// -// Created by Assistant on 10/30/25. -// - -import SpriteKit -import UIKit - -// MARK: - Touch Interaction & Effects -extension CarabinerScene { - - // MARK: - 스와이프 제스처 처리 - - /// 스와이프 제스처 감지 및 처리 - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first else { return } - let location = touch.location(in: self) - - lastTouchLocation = location - lastTouchTime = touch.timestamp - swipeStartLocation = location - - // 터치된 키링 찾기 - if let touchedKeyring = findTouchedKeyring(at: location) { - handleKeyringTouch(keyring: touchedKeyring, at: location) - } - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, - let lastLocation = lastTouchLocation else { return } - - let currentLocation = touch.location(in: self) - let deltaX = currentLocation.x - lastLocation.x - let deltaY = currentLocation.y - lastLocation.y - - // 드래그 중인 키링에 미세한 힘 적용 - if let touchedKeyring = findTouchedKeyring(at: currentLocation) { - applyDragForce(to: touchedKeyring, delta: CGVector(dx: deltaX * 0.1, dy: deltaY * 0.1)) - } - - lastTouchLocation = currentLocation - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, - let startLocation = swipeStartLocation else { return } - - let endLocation = touch.location(in: self) - let swipeVector = CGVector( - dx: endLocation.x - startLocation.x, - dy: endLocation.y - startLocation.y - ) - - let swipeDistance = hypot(swipeVector.dx, swipeVector.dy) - let swipeTime = touch.timestamp - lastTouchTime - - // 스와이프 강도 계산 - if swipeDistance > 50 && swipeTime < 0.5 { - let swipeForce = min(swipeDistance / swipeTime, 1000) // 최대 힘 제한 - handleSwipeGesture(force: swipeForce, direction: swipeVector) - } - - // 초기화 - lastTouchLocation = nil - swipeStartLocation = nil - } - - // MARK: - 키링 찾기 및 상호작용 - - /// 터치 지점에서 키링 찾기 - private func findTouchedKeyring(at location: CGPoint) -> SKNode? { - let touchedNode = atPoint(location) - - // 터치된 노드가 키링의 자식인지 확인 - var currentNode: SKNode? = touchedNode - while let node = currentNode { - if node.name?.hasPrefix("keyring_") == true { - return node - } - currentNode = node.parent - } - - return nil - } - - /// 키링 터치 처리 - private func handleKeyringTouch(keyring: SKNode, at location: CGPoint) { - // 터치된 키링에 미세한 진동 효과 - let vibrationAction = SKAction.sequence([ - SKAction.rotate(byAngle: 0.05, duration: 0.05), - SKAction.rotate(byAngle: -0.1, duration: 0.1), - SKAction.rotate(byAngle: 0.05, duration: 0.05) - ]) - - keyring.run(vibrationAction) - } - - /// 드래그 중 키링에 힘 적용 - private func applyDragForce(to keyring: SKNode, delta: CGVector) { - keyring.enumerateChildNodes(withName: "*") { node, _ in - if let physicsBody = node.physicsBody, physicsBody.isDynamic { - physicsBody.applyForce(delta) - } - } - } - - /// 스와이프 제스처 처리 - private func handleSwipeGesture(force: CGFloat, direction: CGVector) { - let normalizedDirection = normalizeVector(direction) - let swipeForce = CGVector( - dx: normalizedDirection.dx * force * 0.5, - dy: normalizedDirection.dy * force * 0.5 - ) - - // 파티클 효과 (선택사항) - createSwipeParticleEffect(at: lastTouchLocation ?? CGPoint.zero, direction: normalizedDirection) - } - - // MARK: - 유틸리티 메서드 - - /// 벡터 정규화 - private func normalizeVector(_ vector: CGVector) -> CGVector { - let magnitude = hypot(vector.dx, vector.dy) - guard magnitude > 0 else { return CGVector.zero } - - return CGVector(dx: vector.dx / magnitude, dy: vector.dy / magnitude) - } - - /// 스와이프 파티클 효과 생성 - private func createSwipeParticleEffect(at position: CGPoint, direction: CGVector) { - // 간단한 파티클 효과 - for _ in 0..<10 { - let particle = SKShapeNode(circleOfRadius: 2) - particle.fillColor = .systemBlue - particle.alpha = 0.8 - particle.position = position - - addChild(particle) - - // 파티클 움직임 - let moveDistance: CGFloat = 50 - let moveVector = CGVector( - dx: direction.dx * moveDistance + CGFloat.random(in: -20...20), - dy: direction.dy * moveDistance + CGFloat.random(in: -20...20) - ) - - let moveAction = SKAction.move(by: moveVector, duration: 0.5) - let fadeAction = SKAction.fadeOut(withDuration: 0.5) - let removeAction = SKAction.removeFromParent() - - let sequence = SKAction.sequence([ - SKAction.group([moveAction, fadeAction]), - removeAction - ]) - - particle.run(sequence) - } - } -} - -// MARK: - Animation Effects -extension CarabinerScene { - - /// 키링에 흔들기 애니메이션 적용 - func shakeKeyring(at index: Int, intensity: CGFloat = 1.0) { - guard let keyring = getKeyring(at: index) else { return } - - let shakeAction = SKAction.sequence([ - SKAction.moveBy(x: 5 * intensity, y: 0, duration: 0.05), - SKAction.moveBy(x: -10 * intensity, y: 0, duration: 0.1), - SKAction.moveBy(x: 10 * intensity, y: 0, duration: 0.1), - SKAction.moveBy(x: -5 * intensity, y: 0, duration: 0.05) - ]) - - keyring.run(shakeAction) - } - - /// 모든 키링에 흔들기 애니메이션 적용 - func shakeAllKeyrings(intensity: CGFloat = 1.0) { - for (index, _) in keyrings.enumerated() { - shakeKeyring(at: index, intensity: intensity) - } - } - - /// 키링에 탄성 효과 적용 - func bounceKeyring(at index: Int) { - guard let keyring = getKeyring(at: index) else { return } - - let bounceAction = SKAction.sequence([ - SKAction.scale(to: 1.1, duration: 0.1), - SKAction.scale(to: 0.95, duration: 0.1), - SKAction.scale(to: 1.0, duration: 0.1) - ]) - - keyring.run(bounceAction) - } - - /// 카라비너에 회전 애니메이션 적용 - func rotateCarabiner(angle: CGFloat, duration: TimeInterval = 1.0) { - guard let carabiner = carabinerNode else { return } - - let rotateAction = SKAction.rotate(byAngle: angle, duration: duration) - rotateAction.timingMode = .easeInEaseOut - - carabiner.run(rotateAction) - } - - /// 전체 씬에 중력 변경 효과 - func changeGravity(to gravity: CGVector, duration: TimeInterval = 2.0) { - let currentGravity = physicsWorld.gravity - let gravitySteps = 60 // 60 steps for smooth transition - let stepDuration = duration / Double(gravitySteps) - - let deltaX = (gravity.dx - currentGravity.dx) / CGFloat(gravitySteps) - let deltaY = (gravity.dy - currentGravity.dy) / CGFloat(gravitySteps) - - var step = 0 - let timer = Timer.scheduledTimer(withTimeInterval: stepDuration, repeats: true) { timer in - step += 1 - - let newGravityX = currentGravity.dx + deltaX * CGFloat(step) - let newGravityY = currentGravity.dy + deltaY * CGFloat(step) - - self.physicsWorld.gravity = CGVector(dx: newGravityX, dy: newGravityY) - - if step >= gravitySteps { - self.physicsWorld.gravity = gravity - timer.invalidate() - } - } - - timer.fire() - } -} - -// MARK: - Utility -extension CarabinerScene { - - /// 성능 모니터링 - func enablePerformanceMonitoring() { - view?.showsFPS = true - view?.showsNodeCount = true - view?.showsPhysics = true - view?.showsDrawCount = true - } - - /// 물리 바디 시각화 토글 - func togglePhysicsDebug() { - view?.showsPhysics.toggle() - } -} diff --git a/Keychy/Keychy/Core/CarabinerScene+SetUp.swift b/Keychy/Keychy/Core/CarabinerScene+SetUp.swift deleted file mode 100644 index 168455f13..000000000 --- a/Keychy/Keychy/Core/CarabinerScene+SetUp.swift +++ /dev/null @@ -1,429 +0,0 @@ -// -// CarabinerScene+SetUp.swift -// Keychy -// -// Created by 김서현 on 10/29/25. -// - -import SpriteKit - -// MARK: - Setup & Assembly -extension CarabinerScene { - - // 카라비너 + 여러 키링 전체 조립 - func setupCarabinerWithKeyrings() { - let centerX: CGFloat = 0 - // 곱하는 숫자가 높아질 수록 화면 위로 올라가는데, 너무 많이 올리면 화면 밖으로 나가서 아예 안 보이니 주의할 것. - // 0.3 정도가 적당함 - let topY = originalSize.height * 0.3 - - // 1. 카라비너 생성 (루트 노드) - let madeCarabiner = createCarabiner() - madeCarabiner.position = CGPoint(x: centerX, y: topY) - madeCarabiner.physicsBody?.isDynamic = false - containerNode.addChild(madeCarabiner) - carabinerNode = madeCarabiner - - // 2. 키링들 비동기 생성 - createKeyringsAsync(for: madeCarabiner) - } - - // 키링들을 비동기로 생성 (외부에서도 호출 가능하도록 public으로 변경) - func createKeyringsAsync(for carabiner: SKSpriteNode) { - let carabinerSize = carabiner.size - var completedKeyrings = 0 - let totalKeyrings = bodyImages.count - - guard totalKeyrings > 0 else { - onSceneReady?() - return - } - - for (index, bodyImage) in bodyImages.enumerated() { - // Carabiner 모델에서 가져온 비율 (0.0 ~ 1.0 범위) - let nx = getKeyringXPosition(for: index) // 비율 - let ny = getKeyringYPosition(for: index) // 비율 - - // 실제 좌표 = 카라비너 크기 * 비율 - let xOffset = (nx - 0.5) * carabinerSize.width // 중심 기준 - let yOffset = (ny - 0.5) * carabinerSize.height // 중심 기준 - - // 키링 생성 - setupKeyringNode( - bodyImage: bodyImage, - position: CGPoint(x: xOffset, y: yOffset), - parent: carabiner, - index: index - ) { [weak self] keyring in - guard let self = self else { return } - - self.keyrings.append(keyring) - completedKeyrings += 1 - - // 모든 키링이 완성되면 콜백 호출 - if completedKeyrings == totalKeyrings { - DispatchQueue.main.async { - self.onSceneReady?() - } - } - } - } - } - - // 개별 키링 조립 (비동기 처리) - func setupKeyringNode( - bodyImage: UIImage, - position: CGPoint, - parent: SKNode, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - let keyringContainer = SKNode() - keyringContainer.position = position - keyringContainer.name = "keyring_\(index)" - parent.addChild(keyringContainer) - - let centerX: CGFloat = 0 - - // 1. Ring 생성 - KeyringRingComponent.createNode(from: currentRingType) { [weak self] ring in - guard let self = self, let ring = ring else { - completion(keyringContainer) - return - } - // 뭉치함에선 키링의 링 사이즈를 작게 조절함 - //⭐️ TODO: 사이즈 얼마정도가 괜찮을지 싱싱이랑 이야기 해보기 - ring.setScale(0.4) - - // Ring의 상단이 버튼 위치에 오도록 조정 - let ringFrame = ring.calculateAccumulatedFrame() - let ringRadius = ringFrame.height / 2 - - // Ring을 아래로 이동 - ring.position = CGPoint(x: centerX, y: -ringRadius) - ring.physicsBody?.isDynamic = false - keyringContainer.addChild(ring) - - // 2. Chain 생성 (Ring 생성 후) - self.setupChain(ring: ring, centerX: centerX, container: keyringContainer, bodyImage: bodyImage, index: index, completion: completion) - } - } - - // Chain 생성 (Ring 생성 후 호출) - private func setupChain( - ring: SKSpriteNode, - centerX: CGFloat, - container: SKNode, - bodyImage: UIImage, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - - let ringHeight = ring.calculateAccumulatedFrame().height - let ringBottomY = ring.position.y - ringHeight / 2 - let chainStartY = ringBottomY + 0.5 - let chainSpacing: CGFloat = 16 - - KeyringChainComponent.createLinks( - from: currentChainType, - count: 6, - startPosition: CGPoint(x: centerX, y: chainStartY), - spacing: chainSpacing - ) { [weak self] chains in - guard let self = self else { - completion(container) - return - } - - // 체인 노드를 컨테이너에 추가 - for chain in chains { - container.addChild(chain) - } - - // 3. Body 생성 (체인 생성 후) - self.setupBody( - ring: ring, - chains: chains, - centerX: centerX, - chainStartY: chainStartY, - chainSpacing: chainSpacing, - container: container, - bodyImage: bodyImage, - index: index, - completion: completion - ) - } - } - - // Body 생성 및 연결 (Chain 생성 후 호출) - private func setupBody( - ring: SKSpriteNode, - chains: [SKSpriteNode], - centerX: CGFloat, - chainStartY: CGFloat, - chainSpacing: CGFloat, - container: SKNode, - bodyImage: UIImage, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - // UIImage로 Body 생성 - KeyringBodyComponent.createNode(from: bodyImage) { [weak self] body in - guard let self = self, let body = body else { - completion(container) - return - } - body.setScale(0.6) - self.positionAndConnectBody( - body: body, - ring: ring, - chains: chains, - centerX: centerX, - chainStartY: chainStartY, - chainSpacing: chainSpacing, - container: container, - index: index - ) - - completion(container) - } - } - - // Body 위치 설정 및 연결 - private func positionAndConnectBody( - body: SKNode, - ring: SKSpriteNode, - chains: [SKSpriteNode], - centerX: CGFloat, - chainStartY: CGFloat, - chainSpacing: CGFloat, - container: SKNode, - index: Int - ) { - - // Body의 실제 누적 프레임 - let bodyFrame = body.calculateAccumulatedFrame() - let bodyHalfHeight = bodyFrame.height / 2 - - // Body 위치 설정 - let lastChainY = chainStartY - CGFloat(max(chains.count - 1, 0)) * chainSpacing - let lastLinkHeight: CGFloat = chains.last?.calculateAccumulatedFrame().height ?? chainSpacing - let lastChainBottomY = lastChainY - lastLinkHeight / 2 - - let gapByScreen = originalSize.height * 0.01 - let gapByBody = bodyFrame.height * 0.03 - let gap = max(gapByScreen, gapByBody) - - let bodyCenterY = lastChainBottomY - gap - bodyHalfHeight - - body.position = CGPoint(x: centerX, y: bodyCenterY) - container.addChild(body) - - // 조인트 연결 - connectComponents(ring: ring, chains: chains, body: body) - } - - func connectComponents(ring: SKSpriteNode, chains: [SKSpriteNode], body: SKNode) { - if isPhysicsEnabled { - // 물리 시뮬레이션 활성화된 경우: 조인트로 연결하여 움직이게 함 -> 홈화면, 뭉치 디테일뷰 등에서 사용 - connectComponentsWithPhysics(ring: ring, chains: chains, body: body) - } else { - // 물리 시뮬레이션 비활성화된 경우: 모든 구성 요소를 완전히 고정 -> 뭉치 만들기 뷰에서 사용 - fixAllComponents(ring: ring, chains: chains, body: body) - } - } - - // 물리 시뮬레이션 활성화 시: 조인트로 연결 - private func connectComponentsWithPhysics(ring: SKSpriteNode, chains: [SKSpriteNode], body: SKNode) { - var previousNode: SKNode = ring - - // Ring과 첫 번째 Chain 연결 - if let firstChain = chains.first { - let ringWorldPos = containerNode.convert(ring.position, to: self) - let firstChainWorldPos = containerNode.convert(firstChain.position, to: self) - - let joint = SKPhysicsJointPin.joint( - withBodyA: ring.physicsBody!, - bodyB: firstChain.physicsBody!, - anchor: CGPoint( - x: (ringWorldPos.x + firstChainWorldPos.x) / 2, - y: ringWorldPos.y - ) - ) - joint.shouldEnableLimits = false - joint.frictionTorque = 0.2 - physicsWorld.add(joint) - - let distance = hypot( - firstChain.position.x - ring.position.x, - firstChain.position.y - ring.position.y - ) * scaleFactor - - let limitJoint = SKPhysicsJointLimit.joint( - withBodyA: ring.physicsBody!, - bodyB: firstChain.physicsBody!, - anchorA: .zero, - anchorB: .zero - ) - limitJoint.maxLength = distance * 1.05 - physicsWorld.add(limitJoint) - - // 물리 속성 설정 (움직이게 함) - firstChain.physicsBody?.isDynamic = true - firstChain.physicsBody?.affectedByGravity = true - firstChain.physicsBody?.linearDamping = 0.7 - firstChain.physicsBody?.angularDamping = 0.7 - previousNode = firstChain - } - - // Chain 링크들 연결 - for i in 1.. SKSpriteNode { - let carabiner = SKSpriteNode() - - if let image = carabinerImage { - carabiner.texture = SKTexture(image: image) - - // 기기 화면 가로 크기의 0.5배로 카라비너 크기 설정 - let carabinerWidth = screenWidth * 0.5 - // 원본 이미지의 비율 유지 - let aspectRatio = image.size.height / image.size.width - let carabinerHeight = carabinerWidth * aspectRatio - - carabiner.size = CGSize(width: carabinerWidth, height: carabinerHeight) - } - - carabiner.physicsBody = SKPhysicsBody(rectangleOf: carabiner.size) - // 물리 설정, 중력 설정 끔 - carabiner.physicsBody?.isDynamic = false - carabiner.physicsBody?.affectedByGravity = false - - return carabiner - } -} - -// MARK: - 비율 기반 위치 계산 -extension CarabinerScene { - - /// 각 키링의 X 위치 비율 (0.0 ~ 1.0) - func getKeyringXPosition(for index: Int) -> CGFloat { - // Carabiner 모델과 연동 - if let carabiner = carabiner, - index < carabiner.keyringXPosition.count { - return CGFloat(carabiner.keyringXPosition[index]) - } - // 임의로 설정한 기본값 - return 0.5 - } - - /// 각 키링의 Y 위치 비율 (0.0 ~ 1.0) - func getKeyringYPosition(for index: Int) -> CGFloat { - // Carabiner 모델과 연동 - if let carabiner = carabiner, - index < carabiner.keyringYPosition.count { - return CGFloat(carabiner.keyringYPosition[index]) - } - // 임의로 설정한 기본값 - return 0.5 - } -} - -// MARK: - Array Extension (안전한 접근) -extension Array { - subscript(safe index: Int) -> Element? { - indices.contains(index) ? self[index] : nil - } -} diff --git a/Keychy/Keychy/Core/CarabinerScene.swift b/Keychy/Keychy/Core/CarabinerScene.swift deleted file mode 100644 index 846bc1ef5..000000000 --- a/Keychy/Keychy/Core/CarabinerScene.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// CarabinerScene.swift -// Keychy -// -// Created by 김서현 on 10/29/25. -// - -import SpriteKit - -class CarabinerScene: SKScene { - - // MARK: - Properties - var carabinerImage: UIImage? - var bodyImages: [UIImage] = [] - var screenWidth: CGFloat - var carabiner: Carabiner? - - // MARK: - 물리 시뮬레이션 제어 플래그 🎛️ - var isPhysicsEnabled: Bool = true // 기본값은 물리 시뮬레이션 활성화 - - // MARK: - 씬 로딩 완료 콜백 - var onSceneReady: (() -> Void)? - - // MARK: - 크기 조절용 컨테이너 노드 - var containerNode: SKNode! - let scaleFactor: CGFloat - let originalSize = CGSize(width: 393, height: 852) - - /// 원본 사이즈 비율 반환 함수입니다. - var sizeRatio: CGFloat { - return originalSize.height / originalSize.width - } - - // MARK: - 구성 요소들 - var carabinerNode: SKSpriteNode? - var ringNode: SKSpriteNode? - var chainNodes: [SKSpriteNode] = [] - var bodyNode: SKNode? - var keyrings: [SKNode] = [] - - // MARK: - 선택된 타입들 - var currentRingType: RingType = .basic - var currentChainType: ChainType = .basic - var currentBodyType: BodyType = .basic - - // MARK: - 스와이프 제스처 관련 - var lastTouchLocation: CGPoint? - var lastTouchTime: TimeInterval = 0 - var swipeStartLocation: CGPoint? - - // MARK: - Init - init( - carabiner: Carabiner?, - carabinerImage: UIImage?, - ringType: RingType = .basic, - chainType: ChainType = .basic, - bodyType: BodyType = .basic, - bodyImages: [UIImage], - targetSize: CGSize, - screenWidth: CGFloat, - zoomScale: CGFloat = 1.5, - isPhysicsEnabled: Bool = true // 물리 시뮬레이션 활성화 여부 🎛️ - ) { - self.carabiner = carabiner - self.carabinerImage = carabinerImage - self.currentRingType = ringType - self.currentChainType = chainType - self.currentBodyType = bodyType - self.bodyImages = bodyImages.map { $0.fixedOrientation() } - self.screenWidth = screenWidth - self.isPhysicsEnabled = isPhysicsEnabled // 물리 시뮬레이션 설정 저장 🎛️ - - let scaleX = targetSize.width / originalSize.width - let scaleY = targetSize.height / originalSize.height - self.scaleFactor = min(scaleX, scaleY) * zoomScale - - super.init(size: targetSize) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - removeAllChildren() - removeAllActions() - } - - // MARK: - Scene Lifecycle - override func didMove(to view: SKView) { - backgroundColor = .clear - - // 물리 시뮬레이션 설정 분기 처리 - if isPhysicsEnabled { - // 물리 시뮬레이션 활성화 (다른 뷰들용) - physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) - physicsWorld.speed = 1.0 - } else { - // 물리 시뮬레이션 비활성화 (BundleAddKeyringView용) - physicsWorld.gravity = CGVector(dx: 0, dy: 0) - physicsWorld.speed = 0 - } - - // 컨테이너 설정 - containerNode = SKNode() - containerNode.position = CGPoint(x: size.width / 2, y: size.height / 2) - containerNode.setScale(scaleFactor) - addChild(containerNode) - - setupCarabinerWithKeyrings() - - // 씬 로딩 완료 후 콜백 호출 - DispatchQueue.main.async { - self.onSceneReady?() - } - } - - // MARK: - 접근자 메서드들 - - /// 특정 인덱스의 키링 가져오기 - func getKeyring(at index: Int) -> SKNode? { - guard index >= 0 && index < keyrings.count else { return nil } - return keyrings[index] - } - - /// 모든 키링 가져오기 - func getAllKeyrings() -> [SKNode] { - return keyrings - } - - /// 카라비너 가져오기 - func getCarabiner() -> SKSpriteNode? { - return carabinerNode - } - - /// 카라비너의 프레임 정보 반환 (SwiftUI 좌표계로 변환) - func getCarabinerFrame() -> CGRect? { - guard let carabiner = carabinerNode else { return nil } - - // 카라비너의 월드 좌표와 크기 계산 - let worldPos = containerNode.convert(carabiner.position, to: self) - let carabinerWidth = carabiner.size.width * scaleFactor - let carabinerHeight = carabiner.size.height * scaleFactor - - // SpriteKit 좌표계 (원점: 왼쪽 아래) → SwiftUI 좌표계 (원점: 왼쪽 위) 변환 - let swiftUIY = size.height - worldPos.y - carabinerHeight / 2 - - return CGRect( - x: worldPos.x - carabinerWidth / 2, - y: swiftUIY, - width: carabinerWidth, - height: carabinerHeight - ) - } - - // MARK: - 기본 씬 설정 - func setupBasicConfiguration() { - backgroundColor = .clear - physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) - } -} diff --git a/Keychy/Keychy/Core/CarabinerSceneView.swift b/Keychy/Keychy/Core/CarabinerSceneView.swift deleted file mode 100644 index c8b88613c..000000000 --- a/Keychy/Keychy/Core/CarabinerSceneView.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// CarabinerSceneView.swift -// KeytschPrototype -// -// Created by Assistant on 10/30/25. -// - -import SwiftUI -import SpriteKit - -/// CarabinerScene을 SwiftUI에서 사용하기 위한 래퍼 뷰 -struct CarabinerSceneView: View { - - // MARK: - Properties - let carabiner: Carabiner? - let carabinerImage: UIImage? - let bodyImages: [UIImage] - - @State private var scene: CarabinerScene? - @State private var isSceneReady = false - @State private var screenWidth: CGFloat = 0 - - // MARK: - Callbacks - var onSceneReady: (() -> Void)? - var onKeyringTapped: ((Int) -> Void)? - - // MARK: - Init - init( - carabiner: Carabiner? = nil, - carabinerImage: UIImage? = nil, - bodyImages: [UIImage] = [], - onSceneReady: (() -> Void)? = nil, - onKeyringTapped: ((Int) -> Void)? = nil - ) { - self.carabiner = carabiner - self.carabinerImage = carabinerImage - self.bodyImages = bodyImages - self.onSceneReady = onSceneReady - self.onKeyringTapped = onKeyringTapped - } - - // MARK: - Body - var body: some View { - GeometryReader { geometry in - SpriteView(scene: createScene(for: geometry.size)) - .onAppear { - screenWidth = geometry.size.width - } - .overlay { - if !isSceneReady { - LoadingOverlay() - } - } - } - } - - // MARK: - Scene Creation - private func createScene(for size: CGSize) -> CarabinerScene { - if let existingScene = scene { - return existingScene - } - - let newScene = CarabinerScene( - carabiner: carabiner, - carabinerImage: carabinerImage, - bodyImages: bodyImages, - targetSize: size, - screenWidth: screenWidth > 0 ? screenWidth : size.width - ) - - // 콜백 설정 - newScene.onSceneReady = { [weak newScene] in - DispatchQueue.main.async { - self.isSceneReady = true - self.onSceneReady?() - } - } - - scene = newScene - return newScene - } -} - -// MARK: - Loading Overlay -private struct LoadingOverlay: View { - @State private var isAnimating = false - - var body: some View { - VStack(spacing: 16) { - ProgressView() - .scaleEffect(1.2) - .progressViewStyle(CircularProgressViewStyle(tint: .primary)) - - Text("키링을 준비하고 있어요...") - .font(.caption) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black.opacity(0.1)) - .transition(.opacity) - } -} diff --git a/Keychy/Keychy/Core/Components/Keyring/.swift b/Keychy/Keychy/Core/Components/Keyring/.swift deleted file mode 100644 index 95417998e..000000000 --- a/Keychy/Keychy/Core/Components/Keyring/.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// touchesBegan.swift -// Keychy -// -// Created by Jini on 10/31/25. -// - diff --git a/Keychy/Keychy/Core/Components/CategoryTabBar.swift b/Keychy/Keychy/Core/Components/View/CategoryTabBar.swift similarity index 100% rename from Keychy/Keychy/Core/Components/CategoryTabBar.swift rename to Keychy/Keychy/Core/Components/View/CategoryTabBar.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringBodyComponent.swift b/Keychy/Keychy/Core/Keyring/Components/KeyringBodyComponent.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringBodyComponent.swift rename to Keychy/Keychy/Core/Keyring/Components/KeyringBodyComponent.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringChainComponent.swift b/Keychy/Keychy/Core/Keyring/Components/KeyringChainComponent.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringChainComponent.swift rename to Keychy/Keychy/Core/Keyring/Components/KeyringChainComponent.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringRingComponent.swift b/Keychy/Keychy/Core/Keyring/Components/KeyringRingComponent.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringRingComponent.swift rename to Keychy/Keychy/Core/Keyring/Components/KeyringRingComponent.swift diff --git a/Keychy/Keychy/Core/Components/KeyringCellScene+Capture.swift b/Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene+Capture.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringCellScene+Capture.swift rename to Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene+Capture.swift diff --git a/Keychy/Keychy/Core/Components/KeyringCellScene+Setup.swift b/Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene+Setup.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringCellScene+Setup.swift rename to Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene+Setup.swift diff --git a/Keychy/Keychy/Core/Components/KeyringCellScene.swift b/Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringCellScene.swift rename to Keychy/Keychy/Core/Keyring/Scene/CellScene/KeyringCellScene.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringDetailScene+Setup.swift b/Keychy/Keychy/Core/Keyring/Scene/DetailScene/KeyringDetailScene+Setup.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringDetailScene+Setup.swift rename to Keychy/Keychy/Core/Keyring/Scene/DetailScene/KeyringDetailScene+Setup.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringDetailScene.swift b/Keychy/Keychy/Core/Keyring/Scene/DetailScene/KeyringDetailScene.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringDetailScene.swift rename to Keychy/Keychy/Core/Keyring/Scene/DetailScene/KeyringDetailScene.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringScene+Effects.swift b/Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Effects.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringScene+Effects.swift rename to Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Effects.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringScene+Setup.swift b/Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Setup.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringScene+Setup.swift rename to Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Setup.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringScene+Swipe.swift b/Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Swipe.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringScene+Swipe.swift rename to Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Swipe.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringScene+Touch.swift b/Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Touch.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringScene+Touch.swift rename to Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene+Touch.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringScene.swift b/Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringScene.swift rename to Keychy/Keychy/Core/Keyring/Scene/KeyringScene/KeyringScene.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringDetailSceneView.swift b/Keychy/Keychy/Core/Keyring/View/KeyringDetailSceneView.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringDetailSceneView.swift rename to Keychy/Keychy/Core/Keyring/View/KeyringDetailSceneView.swift diff --git a/Keychy/Keychy/Core/Components/Keyring/KeyringSceneView.swift b/Keychy/Keychy/Core/Keyring/View/KeyringSceneView.swift similarity index 100% rename from Keychy/Keychy/Core/Components/Keyring/KeyringSceneView.swift rename to Keychy/Keychy/Core/Keyring/View/KeyringSceneView.swift diff --git a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+Interaction.swift b/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+Interaction.swift deleted file mode 100644 index 769a8c328..000000000 --- a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+Interaction.swift +++ /dev/null @@ -1,254 +0,0 @@ -// -// CarabinerScene+Interaction.swift -// KeytschPrototype -// -// Created by Assistant on 10/30/25. -// - -import SpriteKit -import UIKit - -// MARK: - Touch Interaction & Effects -extension CarabinerScene { - - // MARK: - 스와이프 제스처 처리 - - /// 스와이프 제스처 감지 및 처리 - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first else { return } - let location = touch.location(in: self) - - lastTouchLocation = location - lastTouchTime = touch.timestamp - swipeStartLocation = location - - // 터치된 키링 찾기 - if let touchedKeyring = findTouchedKeyring(at: location) { - handleKeyringTouch(keyring: touchedKeyring, at: location) - } - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, - let lastLocation = lastTouchLocation else { return } - - let currentLocation = touch.location(in: self) - let deltaX = currentLocation.x - lastLocation.x - let deltaY = currentLocation.y - lastLocation.y - - // 드래그 중인 키링에 미세한 힘 적용 - if let touchedKeyring = findTouchedKeyring(at: currentLocation) { - applyDragForce(to: touchedKeyring, delta: CGVector(dx: deltaX * 0.1, dy: deltaY * 0.1)) - } - - lastTouchLocation = currentLocation - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, - let startLocation = swipeStartLocation else { return } - - let endLocation = touch.location(in: self) - let swipeVector = CGVector( - dx: endLocation.x - startLocation.x, - dy: endLocation.y - startLocation.y - ) - - let swipeDistance = hypot(swipeVector.dx, swipeVector.dy) - let swipeTime = touch.timestamp - lastTouchTime - - // 스와이프 강도 계산 - if swipeDistance > 50 && swipeTime < 0.5 { - let swipeForce = min(swipeDistance / swipeTime, 1000) // 최대 힘 제한 - handleSwipeGesture(force: swipeForce, direction: swipeVector) - } - - // 초기화 - lastTouchLocation = nil - swipeStartLocation = nil - } - - // MARK: - 키링 찾기 및 상호작용 - - /// 터치 지점에서 키링 찾기 - private func findTouchedKeyring(at location: CGPoint) -> SKNode? { - let touchedNode = atPoint(location) - - // 터치된 노드가 키링의 자식인지 확인 - var currentNode: SKNode? = touchedNode - while let node = currentNode { - if node.name?.hasPrefix("keyring_") == true { - return node - } - currentNode = node.parent - } - - return nil - } - - /// 키링 터치 처리 - private func handleKeyringTouch(keyring: SKNode, at location: CGPoint) { - // 터치된 키링에 미세한 진동 효과 - let vibrationAction = SKAction.sequence([ - SKAction.rotate(byAngle: 0.05, duration: 0.05), - SKAction.rotate(byAngle: -0.1, duration: 0.1), - SKAction.rotate(byAngle: 0.05, duration: 0.05) - ]) - - keyring.run(vibrationAction) - } - - /// 드래그 중 키링에 힘 적용 - private func applyDragForce(to keyring: SKNode, delta: CGVector) { - keyring.enumerateChildNodes(withName: "*") { node, _ in - if let physicsBody = node.physicsBody, physicsBody.isDynamic { - physicsBody.applyForce(delta) - } - } - } - - /// 스와이프 제스처 처리 - private func handleSwipeGesture(force: CGFloat, direction: CGVector) { - let normalizedDirection = normalizeVector(direction) - let swipeForce = CGVector( - dx: normalizedDirection.dx * force * 0.5, - dy: normalizedDirection.dy * force * 0.5 - ) - - // 파티클 효과 (선택사항) - createSwipeParticleEffect(at: lastTouchLocation ?? CGPoint.zero, direction: normalizedDirection) - } - - // MARK: - 유틸리티 메서드 - - /// 벡터 정규화 - private func normalizeVector(_ vector: CGVector) -> CGVector { - let magnitude = hypot(vector.dx, vector.dy) - guard magnitude > 0 else { return CGVector.zero } - - return CGVector(dx: vector.dx / magnitude, dy: vector.dy / magnitude) - } - - /// 스와이프 파티클 효과 생성 - private func createSwipeParticleEffect(at position: CGPoint, direction: CGVector) { - // 간단한 파티클 효과 - for _ in 0..<10 { - let particle = SKShapeNode(circleOfRadius: 2) - particle.fillColor = .systemBlue - particle.alpha = 0.8 - particle.position = position - - addChild(particle) - - // 파티클 움직임 - let moveDistance: CGFloat = 50 - let moveVector = CGVector( - dx: direction.dx * moveDistance + CGFloat.random(in: -20...20), - dy: direction.dy * moveDistance + CGFloat.random(in: -20...20) - ) - - let moveAction = SKAction.move(by: moveVector, duration: 0.5) - let fadeAction = SKAction.fadeOut(withDuration: 0.5) - let removeAction = SKAction.removeFromParent() - - let sequence = SKAction.sequence([ - SKAction.group([moveAction, fadeAction]), - removeAction - ]) - - particle.run(sequence) - } - } -} - -// MARK: - Animation Effects -extension CarabinerScene { - - /// 키링에 흔들기 애니메이션 적용 - func shakeKeyring(at index: Int, intensity: CGFloat = 1.0) { - guard let keyring = getKeyring(at: index) else { return } - - let shakeAction = SKAction.sequence([ - SKAction.moveBy(x: 5 * intensity, y: 0, duration: 0.05), - SKAction.moveBy(x: -10 * intensity, y: 0, duration: 0.1), - SKAction.moveBy(x: 10 * intensity, y: 0, duration: 0.1), - SKAction.moveBy(x: -5 * intensity, y: 0, duration: 0.05) - ]) - - keyring.run(shakeAction) - } - - /// 모든 키링에 흔들기 애니메이션 적용 - func shakeAllKeyrings(intensity: CGFloat = 1.0) { - for (index, _) in keyrings.enumerated() { - shakeKeyring(at: index, intensity: intensity) - } - } - - /// 키링에 탄성 효과 적용 - func bounceKeyring(at index: Int) { - guard let keyring = getKeyring(at: index) else { return } - - let bounceAction = SKAction.sequence([ - SKAction.scale(to: 1.1, duration: 0.1), - SKAction.scale(to: 0.95, duration: 0.1), - SKAction.scale(to: 1.0, duration: 0.1) - ]) - - keyring.run(bounceAction) - } - - /// 카라비너에 회전 애니메이션 적용 - func rotateCarabiner(angle: CGFloat, duration: TimeInterval = 1.0) { - guard let carabiner = carabinerNode else { return } - - let rotateAction = SKAction.rotate(byAngle: angle, duration: duration) - rotateAction.timingMode = .easeInEaseOut - - carabiner.run(rotateAction) - } - - /// 전체 씬에 중력 변경 효과 - func changeGravity(to gravity: CGVector, duration: TimeInterval = 2.0) { - let currentGravity = physicsWorld.gravity - let gravitySteps = 60 // 60 steps for smooth transition - let stepDuration = duration / Double(gravitySteps) - - let deltaX = (gravity.dx - currentGravity.dx) / CGFloat(gravitySteps) - let deltaY = (gravity.dy - currentGravity.dy) / CGFloat(gravitySteps) - - var step = 0 - let timer = Timer.scheduledTimer(withTimeInterval: stepDuration, repeats: true) { timer in - step += 1 - - let newGravityX = currentGravity.dx + deltaX * CGFloat(step) - let newGravityY = currentGravity.dy + deltaY * CGFloat(step) - - self.physicsWorld.gravity = CGVector(dx: newGravityX, dy: newGravityY) - - if step >= gravitySteps { - self.physicsWorld.gravity = gravity - timer.invalidate() - } - } - - timer.fire() - } -} - -// MARK: - Utility -extension CarabinerScene { - - /// 성능 모니터링 - func enablePerformanceMonitoring() { - view?.showsFPS = true - view?.showsNodeCount = true - view?.showsPhysics = true - view?.showsDrawCount = true - } - - /// 물리 바디 시각화 토글 - func togglePhysicsDebug() { - view?.showsPhysics.toggle() - } -} diff --git a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+SetUp.swift b/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+SetUp.swift deleted file mode 100644 index 168455f13..000000000 --- a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene+SetUp.swift +++ /dev/null @@ -1,429 +0,0 @@ -// -// CarabinerScene+SetUp.swift -// Keychy -// -// Created by 김서현 on 10/29/25. -// - -import SpriteKit - -// MARK: - Setup & Assembly -extension CarabinerScene { - - // 카라비너 + 여러 키링 전체 조립 - func setupCarabinerWithKeyrings() { - let centerX: CGFloat = 0 - // 곱하는 숫자가 높아질 수록 화면 위로 올라가는데, 너무 많이 올리면 화면 밖으로 나가서 아예 안 보이니 주의할 것. - // 0.3 정도가 적당함 - let topY = originalSize.height * 0.3 - - // 1. 카라비너 생성 (루트 노드) - let madeCarabiner = createCarabiner() - madeCarabiner.position = CGPoint(x: centerX, y: topY) - madeCarabiner.physicsBody?.isDynamic = false - containerNode.addChild(madeCarabiner) - carabinerNode = madeCarabiner - - // 2. 키링들 비동기 생성 - createKeyringsAsync(for: madeCarabiner) - } - - // 키링들을 비동기로 생성 (외부에서도 호출 가능하도록 public으로 변경) - func createKeyringsAsync(for carabiner: SKSpriteNode) { - let carabinerSize = carabiner.size - var completedKeyrings = 0 - let totalKeyrings = bodyImages.count - - guard totalKeyrings > 0 else { - onSceneReady?() - return - } - - for (index, bodyImage) in bodyImages.enumerated() { - // Carabiner 모델에서 가져온 비율 (0.0 ~ 1.0 범위) - let nx = getKeyringXPosition(for: index) // 비율 - let ny = getKeyringYPosition(for: index) // 비율 - - // 실제 좌표 = 카라비너 크기 * 비율 - let xOffset = (nx - 0.5) * carabinerSize.width // 중심 기준 - let yOffset = (ny - 0.5) * carabinerSize.height // 중심 기준 - - // 키링 생성 - setupKeyringNode( - bodyImage: bodyImage, - position: CGPoint(x: xOffset, y: yOffset), - parent: carabiner, - index: index - ) { [weak self] keyring in - guard let self = self else { return } - - self.keyrings.append(keyring) - completedKeyrings += 1 - - // 모든 키링이 완성되면 콜백 호출 - if completedKeyrings == totalKeyrings { - DispatchQueue.main.async { - self.onSceneReady?() - } - } - } - } - } - - // 개별 키링 조립 (비동기 처리) - func setupKeyringNode( - bodyImage: UIImage, - position: CGPoint, - parent: SKNode, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - let keyringContainer = SKNode() - keyringContainer.position = position - keyringContainer.name = "keyring_\(index)" - parent.addChild(keyringContainer) - - let centerX: CGFloat = 0 - - // 1. Ring 생성 - KeyringRingComponent.createNode(from: currentRingType) { [weak self] ring in - guard let self = self, let ring = ring else { - completion(keyringContainer) - return - } - // 뭉치함에선 키링의 링 사이즈를 작게 조절함 - //⭐️ TODO: 사이즈 얼마정도가 괜찮을지 싱싱이랑 이야기 해보기 - ring.setScale(0.4) - - // Ring의 상단이 버튼 위치에 오도록 조정 - let ringFrame = ring.calculateAccumulatedFrame() - let ringRadius = ringFrame.height / 2 - - // Ring을 아래로 이동 - ring.position = CGPoint(x: centerX, y: -ringRadius) - ring.physicsBody?.isDynamic = false - keyringContainer.addChild(ring) - - // 2. Chain 생성 (Ring 생성 후) - self.setupChain(ring: ring, centerX: centerX, container: keyringContainer, bodyImage: bodyImage, index: index, completion: completion) - } - } - - // Chain 생성 (Ring 생성 후 호출) - private func setupChain( - ring: SKSpriteNode, - centerX: CGFloat, - container: SKNode, - bodyImage: UIImage, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - - let ringHeight = ring.calculateAccumulatedFrame().height - let ringBottomY = ring.position.y - ringHeight / 2 - let chainStartY = ringBottomY + 0.5 - let chainSpacing: CGFloat = 16 - - KeyringChainComponent.createLinks( - from: currentChainType, - count: 6, - startPosition: CGPoint(x: centerX, y: chainStartY), - spacing: chainSpacing - ) { [weak self] chains in - guard let self = self else { - completion(container) - return - } - - // 체인 노드를 컨테이너에 추가 - for chain in chains { - container.addChild(chain) - } - - // 3. Body 생성 (체인 생성 후) - self.setupBody( - ring: ring, - chains: chains, - centerX: centerX, - chainStartY: chainStartY, - chainSpacing: chainSpacing, - container: container, - bodyImage: bodyImage, - index: index, - completion: completion - ) - } - } - - // Body 생성 및 연결 (Chain 생성 후 호출) - private func setupBody( - ring: SKSpriteNode, - chains: [SKSpriteNode], - centerX: CGFloat, - chainStartY: CGFloat, - chainSpacing: CGFloat, - container: SKNode, - bodyImage: UIImage, - index: Int, - completion: @escaping (SKNode) -> Void - ) { - // UIImage로 Body 생성 - KeyringBodyComponent.createNode(from: bodyImage) { [weak self] body in - guard let self = self, let body = body else { - completion(container) - return - } - body.setScale(0.6) - self.positionAndConnectBody( - body: body, - ring: ring, - chains: chains, - centerX: centerX, - chainStartY: chainStartY, - chainSpacing: chainSpacing, - container: container, - index: index - ) - - completion(container) - } - } - - // Body 위치 설정 및 연결 - private func positionAndConnectBody( - body: SKNode, - ring: SKSpriteNode, - chains: [SKSpriteNode], - centerX: CGFloat, - chainStartY: CGFloat, - chainSpacing: CGFloat, - container: SKNode, - index: Int - ) { - - // Body의 실제 누적 프레임 - let bodyFrame = body.calculateAccumulatedFrame() - let bodyHalfHeight = bodyFrame.height / 2 - - // Body 위치 설정 - let lastChainY = chainStartY - CGFloat(max(chains.count - 1, 0)) * chainSpacing - let lastLinkHeight: CGFloat = chains.last?.calculateAccumulatedFrame().height ?? chainSpacing - let lastChainBottomY = lastChainY - lastLinkHeight / 2 - - let gapByScreen = originalSize.height * 0.01 - let gapByBody = bodyFrame.height * 0.03 - let gap = max(gapByScreen, gapByBody) - - let bodyCenterY = lastChainBottomY - gap - bodyHalfHeight - - body.position = CGPoint(x: centerX, y: bodyCenterY) - container.addChild(body) - - // 조인트 연결 - connectComponents(ring: ring, chains: chains, body: body) - } - - func connectComponents(ring: SKSpriteNode, chains: [SKSpriteNode], body: SKNode) { - if isPhysicsEnabled { - // 물리 시뮬레이션 활성화된 경우: 조인트로 연결하여 움직이게 함 -> 홈화면, 뭉치 디테일뷰 등에서 사용 - connectComponentsWithPhysics(ring: ring, chains: chains, body: body) - } else { - // 물리 시뮬레이션 비활성화된 경우: 모든 구성 요소를 완전히 고정 -> 뭉치 만들기 뷰에서 사용 - fixAllComponents(ring: ring, chains: chains, body: body) - } - } - - // 물리 시뮬레이션 활성화 시: 조인트로 연결 - private func connectComponentsWithPhysics(ring: SKSpriteNode, chains: [SKSpriteNode], body: SKNode) { - var previousNode: SKNode = ring - - // Ring과 첫 번째 Chain 연결 - if let firstChain = chains.first { - let ringWorldPos = containerNode.convert(ring.position, to: self) - let firstChainWorldPos = containerNode.convert(firstChain.position, to: self) - - let joint = SKPhysicsJointPin.joint( - withBodyA: ring.physicsBody!, - bodyB: firstChain.physicsBody!, - anchor: CGPoint( - x: (ringWorldPos.x + firstChainWorldPos.x) / 2, - y: ringWorldPos.y - ) - ) - joint.shouldEnableLimits = false - joint.frictionTorque = 0.2 - physicsWorld.add(joint) - - let distance = hypot( - firstChain.position.x - ring.position.x, - firstChain.position.y - ring.position.y - ) * scaleFactor - - let limitJoint = SKPhysicsJointLimit.joint( - withBodyA: ring.physicsBody!, - bodyB: firstChain.physicsBody!, - anchorA: .zero, - anchorB: .zero - ) - limitJoint.maxLength = distance * 1.05 - physicsWorld.add(limitJoint) - - // 물리 속성 설정 (움직이게 함) - firstChain.physicsBody?.isDynamic = true - firstChain.physicsBody?.affectedByGravity = true - firstChain.physicsBody?.linearDamping = 0.7 - firstChain.physicsBody?.angularDamping = 0.7 - previousNode = firstChain - } - - // Chain 링크들 연결 - for i in 1.. SKSpriteNode { - let carabiner = SKSpriteNode() - - if let image = carabinerImage { - carabiner.texture = SKTexture(image: image) - - // 기기 화면 가로 크기의 0.5배로 카라비너 크기 설정 - let carabinerWidth = screenWidth * 0.5 - // 원본 이미지의 비율 유지 - let aspectRatio = image.size.height / image.size.width - let carabinerHeight = carabinerWidth * aspectRatio - - carabiner.size = CGSize(width: carabinerWidth, height: carabinerHeight) - } - - carabiner.physicsBody = SKPhysicsBody(rectangleOf: carabiner.size) - // 물리 설정, 중력 설정 끔 - carabiner.physicsBody?.isDynamic = false - carabiner.physicsBody?.affectedByGravity = false - - return carabiner - } -} - -// MARK: - 비율 기반 위치 계산 -extension CarabinerScene { - - /// 각 키링의 X 위치 비율 (0.0 ~ 1.0) - func getKeyringXPosition(for index: Int) -> CGFloat { - // Carabiner 모델과 연동 - if let carabiner = carabiner, - index < carabiner.keyringXPosition.count { - return CGFloat(carabiner.keyringXPosition[index]) - } - // 임의로 설정한 기본값 - return 0.5 - } - - /// 각 키링의 Y 위치 비율 (0.0 ~ 1.0) - func getKeyringYPosition(for index: Int) -> CGFloat { - // Carabiner 모델과 연동 - if let carabiner = carabiner, - index < carabiner.keyringYPosition.count { - return CGFloat(carabiner.keyringYPosition[index]) - } - // 임의로 설정한 기본값 - return 0.5 - } -} - -// MARK: - Array Extension (안전한 접근) -extension Array { - subscript(safe index: Int) -> Element? { - indices.contains(index) ? self[index] : nil - } -} diff --git a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene.swift b/Keychy/Keychy/Core/KeyringBundle/CarabinerScene.swift deleted file mode 100644 index 846bc1ef5..000000000 --- a/Keychy/Keychy/Core/KeyringBundle/CarabinerScene.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// CarabinerScene.swift -// Keychy -// -// Created by 김서현 on 10/29/25. -// - -import SpriteKit - -class CarabinerScene: SKScene { - - // MARK: - Properties - var carabinerImage: UIImage? - var bodyImages: [UIImage] = [] - var screenWidth: CGFloat - var carabiner: Carabiner? - - // MARK: - 물리 시뮬레이션 제어 플래그 🎛️ - var isPhysicsEnabled: Bool = true // 기본값은 물리 시뮬레이션 활성화 - - // MARK: - 씬 로딩 완료 콜백 - var onSceneReady: (() -> Void)? - - // MARK: - 크기 조절용 컨테이너 노드 - var containerNode: SKNode! - let scaleFactor: CGFloat - let originalSize = CGSize(width: 393, height: 852) - - /// 원본 사이즈 비율 반환 함수입니다. - var sizeRatio: CGFloat { - return originalSize.height / originalSize.width - } - - // MARK: - 구성 요소들 - var carabinerNode: SKSpriteNode? - var ringNode: SKSpriteNode? - var chainNodes: [SKSpriteNode] = [] - var bodyNode: SKNode? - var keyrings: [SKNode] = [] - - // MARK: - 선택된 타입들 - var currentRingType: RingType = .basic - var currentChainType: ChainType = .basic - var currentBodyType: BodyType = .basic - - // MARK: - 스와이프 제스처 관련 - var lastTouchLocation: CGPoint? - var lastTouchTime: TimeInterval = 0 - var swipeStartLocation: CGPoint? - - // MARK: - Init - init( - carabiner: Carabiner?, - carabinerImage: UIImage?, - ringType: RingType = .basic, - chainType: ChainType = .basic, - bodyType: BodyType = .basic, - bodyImages: [UIImage], - targetSize: CGSize, - screenWidth: CGFloat, - zoomScale: CGFloat = 1.5, - isPhysicsEnabled: Bool = true // 물리 시뮬레이션 활성화 여부 🎛️ - ) { - self.carabiner = carabiner - self.carabinerImage = carabinerImage - self.currentRingType = ringType - self.currentChainType = chainType - self.currentBodyType = bodyType - self.bodyImages = bodyImages.map { $0.fixedOrientation() } - self.screenWidth = screenWidth - self.isPhysicsEnabled = isPhysicsEnabled // 물리 시뮬레이션 설정 저장 🎛️ - - let scaleX = targetSize.width / originalSize.width - let scaleY = targetSize.height / originalSize.height - self.scaleFactor = min(scaleX, scaleY) * zoomScale - - super.init(size: targetSize) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - removeAllChildren() - removeAllActions() - } - - // MARK: - Scene Lifecycle - override func didMove(to view: SKView) { - backgroundColor = .clear - - // 물리 시뮬레이션 설정 분기 처리 - if isPhysicsEnabled { - // 물리 시뮬레이션 활성화 (다른 뷰들용) - physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) - physicsWorld.speed = 1.0 - } else { - // 물리 시뮬레이션 비활성화 (BundleAddKeyringView용) - physicsWorld.gravity = CGVector(dx: 0, dy: 0) - physicsWorld.speed = 0 - } - - // 컨테이너 설정 - containerNode = SKNode() - containerNode.position = CGPoint(x: size.width / 2, y: size.height / 2) - containerNode.setScale(scaleFactor) - addChild(containerNode) - - setupCarabinerWithKeyrings() - - // 씬 로딩 완료 후 콜백 호출 - DispatchQueue.main.async { - self.onSceneReady?() - } - } - - // MARK: - 접근자 메서드들 - - /// 특정 인덱스의 키링 가져오기 - func getKeyring(at index: Int) -> SKNode? { - guard index >= 0 && index < keyrings.count else { return nil } - return keyrings[index] - } - - /// 모든 키링 가져오기 - func getAllKeyrings() -> [SKNode] { - return keyrings - } - - /// 카라비너 가져오기 - func getCarabiner() -> SKSpriteNode? { - return carabinerNode - } - - /// 카라비너의 프레임 정보 반환 (SwiftUI 좌표계로 변환) - func getCarabinerFrame() -> CGRect? { - guard let carabiner = carabinerNode else { return nil } - - // 카라비너의 월드 좌표와 크기 계산 - let worldPos = containerNode.convert(carabiner.position, to: self) - let carabinerWidth = carabiner.size.width * scaleFactor - let carabinerHeight = carabiner.size.height * scaleFactor - - // SpriteKit 좌표계 (원점: 왼쪽 아래) → SwiftUI 좌표계 (원점: 왼쪽 위) 변환 - let swiftUIY = size.height - worldPos.y - carabinerHeight / 2 - - return CGRect( - x: worldPos.x - carabinerWidth / 2, - y: swiftUIY, - width: carabinerWidth, - height: carabinerHeight - ) - } - - // MARK: - 기본 씬 설정 - func setupBasicConfiguration() { - backgroundColor = .clear - physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) - } -} diff --git a/Keychy/Keychy/Core/KeyringBundle/CarabinerSceneView.swift b/Keychy/Keychy/Core/KeyringBundle/CarabinerSceneView.swift deleted file mode 100644 index c8b88613c..000000000 --- a/Keychy/Keychy/Core/KeyringBundle/CarabinerSceneView.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// CarabinerSceneView.swift -// KeytschPrototype -// -// Created by Assistant on 10/30/25. -// - -import SwiftUI -import SpriteKit - -/// CarabinerScene을 SwiftUI에서 사용하기 위한 래퍼 뷰 -struct CarabinerSceneView: View { - - // MARK: - Properties - let carabiner: Carabiner? - let carabinerImage: UIImage? - let bodyImages: [UIImage] - - @State private var scene: CarabinerScene? - @State private var isSceneReady = false - @State private var screenWidth: CGFloat = 0 - - // MARK: - Callbacks - var onSceneReady: (() -> Void)? - var onKeyringTapped: ((Int) -> Void)? - - // MARK: - Init - init( - carabiner: Carabiner? = nil, - carabinerImage: UIImage? = nil, - bodyImages: [UIImage] = [], - onSceneReady: (() -> Void)? = nil, - onKeyringTapped: ((Int) -> Void)? = nil - ) { - self.carabiner = carabiner - self.carabinerImage = carabinerImage - self.bodyImages = bodyImages - self.onSceneReady = onSceneReady - self.onKeyringTapped = onKeyringTapped - } - - // MARK: - Body - var body: some View { - GeometryReader { geometry in - SpriteView(scene: createScene(for: geometry.size)) - .onAppear { - screenWidth = geometry.size.width - } - .overlay { - if !isSceneReady { - LoadingOverlay() - } - } - } - } - - // MARK: - Scene Creation - private func createScene(for size: CGSize) -> CarabinerScene { - if let existingScene = scene { - return existingScene - } - - let newScene = CarabinerScene( - carabiner: carabiner, - carabinerImage: carabinerImage, - bodyImages: bodyImages, - targetSize: size, - screenWidth: screenWidth > 0 ? screenWidth : size.width - ) - - // 콜백 설정 - newScene.onSceneReady = { [weak newScene] in - DispatchQueue.main.async { - self.isSceneReady = true - self.onSceneReady?() - } - } - - scene = newScene - return newScene - } -} - -// MARK: - Loading Overlay -private struct LoadingOverlay: View { - @State private var isAnimating = false - - var body: some View { - VStack(spacing: 16) { - ProgressView() - .scaleEffect(1.2) - .progressViewStyle(CircularProgressViewStyle(tint: .primary)) - - Text("키링을 준비하고 있어요...") - .font(.caption) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black.opacity(0.1)) - .transition(.opacity) - } -} diff --git a/Keychy/Keychy/Core/Components/KeyringBundle/BundleRingComponent.swift b/Keychy/Keychy/Core/KeyringBundle/Components/BundleRingComponent.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringBundle/BundleRingComponent.swift rename to Keychy/Keychy/Core/KeyringBundle/Components/BundleRingComponent.swift diff --git a/Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringCaptureScene+Capture.swift b/Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringCaptureScene+Capture.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringCaptureScene+Capture.swift rename to Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringCaptureScene+Capture.swift diff --git a/Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringCaptureScene.swift b/Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringCaptureScene.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringCaptureScene.swift rename to Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringCaptureScene.swift diff --git a/Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringScene.swift b/Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringScene.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringScene.swift rename to Keychy/Keychy/Core/KeyringBundle/Scene/MultiKeyringScene.swift diff --git a/Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringSceneView.swift b/Keychy/Keychy/Core/KeyringBundle/View/MultiKeyringSceneView.swift similarity index 100% rename from Keychy/Keychy/Core/Components/KeyringBundle/MultiKeyringSceneView.swift rename to Keychy/Keychy/Core/KeyringBundle/View/MultiKeyringSceneView.swift