diff --git a/Keychy/Keychy.xcodeproj/project.pbxproj b/Keychy/Keychy.xcodeproj/project.pbxproj index 0cc4926a7..83242c0e5 100644 --- a/Keychy/Keychy.xcodeproj/project.pbxproj +++ b/Keychy/Keychy.xcodeproj/project.pbxproj @@ -120,7 +120,7 @@ 4C4733242F1FA2AB005D2376 /* WorkshopPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733212F1FA2AB005D2376 /* WorkshopPreview.swift */; }; 4C4733252F1FA2AB005D2376 /* MyItemsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733202F1FA2AB005D2376 /* MyItemsView.swift */; }; 4C4733262F1FA2AB005D2376 /* WorkshopGridHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733182F1FA2AB005D2376 /* WorkshopGridHelpers.swift */; }; - 4C4733272F1FA2AB005D2376 /* WorkshopMakingKeyringSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C47331A2F1FA2AB005D2376 /* WorkshopMakingKeyringSection.swift */; }; + 4C4733272F1FA2AB005D2376 /* WorkshopBundleBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C47331A2F1FA2AB005D2376 /* WorkshopBundleBanner.swift */; }; 4C4733282F1FA2AB005D2376 /* WorkshopTemplatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C47331C2F1FA2AB005D2376 /* WorkshopTemplatesView.swift */; }; 4C4733292F1FA2AB005D2376 /* WorkshopMainContentSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733192F1FA2AB005D2376 /* WorkshopMainContentSection.swift */; }; 4C47332A2F1FA2AB005D2376 /* WorkshopStickyHeaderSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C47331B2F1FA2AB005D2376 /* WorkshopStickyHeaderSection.swift */; }; @@ -197,6 +197,7 @@ 4C4733D72F1FA388005D2376 /* AcrylicPhotoVM+Crop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733522F1FA388005D2376 /* AcrylicPhotoVM+Crop.swift */; }; 4C4733D82F1FA388005D2376 /* AcrylicPhotoPreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C47335A2F1FA388005D2376 /* AcrylicPhotoPreView.swift */; }; 4C4733D92F1FA388005D2376 /* TemplatePreviewComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733392F1FA388005D2376 /* TemplatePreviewComponents.swift */; }; + 4C4733E52F20FE34005D2376 /* WorkshopRecentTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4733E42F20FE34005D2376 /* WorkshopRecentTemplate.swift */; }; 4C65303B2EBA5FA0000F8154 /* CheckmarkAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C65303A2EBA5FA0000F8154 /* CheckmarkAlert.swift */; }; 4C65303E2EBA6042000F8154 /* ImageSaveAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C65303D2EBA6042000F8154 /* ImageSaveAlert.swift */; }; 4C6530442EBA8077000F8154 /* PurchaseSuccessAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6530432EBA8077000F8154 /* PurchaseSuccessAlert.swift */; }; @@ -549,7 +550,7 @@ 4C4733162F1FA2AB005D2376 /* WorkshopViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopViewModel.swift; sourceTree = ""; }; 4C4733182F1FA2AB005D2376 /* WorkshopGridHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopGridHelpers.swift; sourceTree = ""; }; 4C4733192F1FA2AB005D2376 /* WorkshopMainContentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopMainContentSection.swift; sourceTree = ""; }; - 4C47331A2F1FA2AB005D2376 /* WorkshopMakingKeyringSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopMakingKeyringSection.swift; sourceTree = ""; }; + 4C47331A2F1FA2AB005D2376 /* WorkshopBundleBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopBundleBanner.swift; sourceTree = ""; }; 4C47331B2F1FA2AB005D2376 /* WorkshopStickyHeaderSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopStickyHeaderSection.swift; sourceTree = ""; }; 4C47331C2F1FA2AB005D2376 /* WorkshopTemplatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopTemplatesView.swift; sourceTree = ""; }; 4C47331D2F1FA2AB005D2376 /* WorkshopTopBannerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopTopBannerSection.swift; sourceTree = ""; }; @@ -626,6 +627,7 @@ 4C47338B2F1FA388005D2376 /* SpeechBubbleFramePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechBubbleFramePreviewView.swift; sourceTree = ""; }; 4C47338C2F1FA388005D2376 /* SpeechBubbleFrameSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechBubbleFrameSelectorView.swift; sourceTree = ""; }; 4C47338D2F1FA388005D2376 /* SpeechBubblePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechBubblePreview.swift; sourceTree = ""; }; + 4C4733E42F20FE34005D2376 /* WorkshopRecentTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkshopRecentTemplate.swift; sourceTree = ""; }; 4C65303A2EBA5FA0000F8154 /* CheckmarkAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkAlert.swift; sourceTree = ""; }; 4C65303D2EBA6042000F8154 /* ImageSaveAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSaveAlert.swift; sourceTree = ""; }; 4C6530432EBA8077000F8154 /* PurchaseSuccessAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseSuccessAlert.swift; sourceTree = ""; }; @@ -1093,13 +1095,14 @@ 4C47331F2F1FA2AB005D2376 /* Main */ = { isa = PBXGroup; children = ( - 4C4733182F1FA2AB005D2376 /* WorkshopGridHelpers.swift */, + 4C47331E2F1FA2AB005D2376 /* WorkshopView.swift */, + 4C47331D2F1FA2AB005D2376 /* WorkshopTopBannerSection.swift */, 4C4733192F1FA2AB005D2376 /* WorkshopMainContentSection.swift */, - 4C47331A2F1FA2AB005D2376 /* WorkshopMakingKeyringSection.swift */, 4C47331B2F1FA2AB005D2376 /* WorkshopStickyHeaderSection.swift */, 4C47331C2F1FA2AB005D2376 /* WorkshopTemplatesView.swift */, - 4C47331D2F1FA2AB005D2376 /* WorkshopTopBannerSection.swift */, - 4C47331E2F1FA2AB005D2376 /* WorkshopView.swift */, + 4C4733182F1FA2AB005D2376 /* WorkshopGridHelpers.swift */, + 4C4733202F1FA2AB005D2376 /* MyItemsView.swift */, + 4C4733212F1FA2AB005D2376 /* WorkshopPreview.swift */, ); path = Main; sourceTree = ""; @@ -1107,9 +1110,10 @@ 4C4733222F1FA2AB005D2376 /* Views */ = { isa = PBXGroup; children = ( + 4C4733152F1FA2AB005D2376 /* Components */, + 4C4733E02F20FD18005D2376 /* Keyring */, + 4C4733E22F20FD50005D2376 /* Bundle */, 4C47331F2F1FA2AB005D2376 /* Main */, - 4C4733202F1FA2AB005D2376 /* MyItemsView.swift */, - 4C4733212F1FA2AB005D2376 /* WorkshopPreview.swift */, ); path = Views; sourceTree = ""; @@ -1399,6 +1403,22 @@ path = KeyringMaker; sourceTree = ""; }; + 4C4733E02F20FD18005D2376 /* Keyring */ = { + isa = PBXGroup; + children = ( + 4C4733E42F20FE34005D2376 /* WorkshopRecentTemplate.swift */, + ); + path = Keyring; + sourceTree = ""; + }; + 4C4733E22F20FD50005D2376 /* Bundle */ = { + isa = PBXGroup; + children = ( + 4C47331A2F1FA2AB005D2376 /* WorkshopBundleBanner.swift */, + ); + path = Bundle; + sourceTree = ""; + }; 4C65303C2EBA5FF3000F8154 /* Alerts */ = { isa = PBXGroup; children = ( @@ -1963,7 +1983,6 @@ 4CEC62682EAE08DF0099ECEE /* Workshop */ = { isa = PBXGroup; children = ( - 4C4733152F1FA2AB005D2376 /* Components */, 4C4733172F1FA2AB005D2376 /* ViewModels */, 4C4733222F1FA2AB005D2376 /* Views */, C665DDE92EAEFAA800CE4495 /* Coin */, @@ -2394,6 +2413,7 @@ 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 */, @@ -2560,7 +2580,7 @@ 4C4733242F1FA2AB005D2376 /* WorkshopPreview.swift in Sources */, 4C4733252F1FA2AB005D2376 /* MyItemsView.swift in Sources */, 4C4733262F1FA2AB005D2376 /* WorkshopGridHelpers.swift in Sources */, - 4C4733272F1FA2AB005D2376 /* WorkshopMakingKeyringSection.swift in Sources */, + 4C4733272F1FA2AB005D2376 /* WorkshopBundleBanner.swift in Sources */, 4C4733282F1FA2AB005D2376 /* WorkshopTemplatesView.swift in Sources */, 4C4733292F1FA2AB005D2376 /* WorkshopMainContentSection.swift in Sources */, 4C47332A2F1FA2AB005D2376 /* WorkshopStickyHeaderSection.swift in Sources */, diff --git a/Keychy/Keychy/CommonModels/User/KeychyUser.swift b/Keychy/Keychy/CommonModels/User/KeychyUser.swift index 252a1e9b0..3f95959be 100644 --- a/Keychy/Keychy/CommonModels/User/KeychyUser.swift +++ b/Keychy/Keychy/CommonModels/User/KeychyUser.swift @@ -27,6 +27,7 @@ struct KeychyUser: Identifiable { var carabiners: [String] var tags: [String] var keyrings: [String] + var recentTemplates: [String] // 최근 사용 템플릿 ID (최대 5개, 최신순) var termsAgreed: Bool // 필수 약관 동의 여부 var marketingAgreed: Bool // 마케팅 수신 동의 여부 @@ -48,6 +49,7 @@ struct KeychyUser: Identifiable { "carabiners": carabiners, "tags": tags, "keyrings": keyrings, + "recentTemplates": recentTemplates, "termsAgreed": termsAgreed, "marketingAgreed": marketingAgreed ] @@ -77,6 +79,7 @@ struct KeychyUser: Identifiable { self.carabiners = data["carabiners"] as? [String] ?? [] self.tags = data["tags"] as? [String] ?? [] self.keyrings = data["keyrings"] as? [String] ?? [] + self.recentTemplates = data["recentTemplates"] as? [String] ?? [] self.termsAgreed = data["termsAgreed"] as? Bool ?? false self.marketingAgreed = data["marketingAgreed"] as? Bool ?? false } @@ -99,6 +102,7 @@ struct KeychyUser: Identifiable { self.carabiners = [] self.tags = [] self.keyrings = [] + self.recentTemplates = [] self.termsAgreed = false self.marketingAgreed = false } diff --git a/Keychy/Keychy/Core/DesignSystem/Typography/Typography.swift b/Keychy/Keychy/Core/DesignSystem/Typography/Typography.swift index 692c830e4..3d3422ce0 100644 --- a/Keychy/Keychy/Core/DesignSystem/Typography/Typography.swift +++ b/Keychy/Keychy/Core/DesignSystem/Typography/Typography.swift @@ -81,6 +81,7 @@ struct Typography { static let nanum10EB12 = Typography(font: .custom(.nanumExtraBold, size: 10), lineSpacing: 2) static let nanum18EB12 = Typography(font: .custom(.nanumExtraBold, size: 18), lineSpacing: 2) + static let nanum24EB = Typography(font: .custom(.nanumExtraBold, size: 24), lineSpacing: 0) static let nanum32EB = Typography(font: .custom(.nanumExtraBold, size: 32), lineSpacing: 2) // MARK: - Pretendard diff --git a/Keychy/Keychy/Presentation/KeyringMaker/Shared/Views/KeyringInfoInputView+FirebaseSave.swift b/Keychy/Keychy/Presentation/KeyringMaker/Shared/Views/KeyringInfoInputView+FirebaseSave.swift index 69dba80fc..b25439bf5 100644 --- a/Keychy/Keychy/Presentation/KeyringMaker/Shared/Views/KeyringInfoInputView+FirebaseSave.swift +++ b/Keychy/Keychy/Presentation/KeyringMaker/Shared/Views/KeyringInfoInputView+FirebaseSave.swift @@ -194,6 +194,12 @@ extension KeyringInfoInputView { soundId: soundId, particleId: particleId ) + + // 최근 사용 템플릿 업데이트 + if !selectedTemplate.isEmpty { + self.updateRecentTemplates(uid: uid, templateId: selectedTemplate) + } + completion(true, keyringId) } else { completion(false, nil) @@ -273,6 +279,44 @@ extension KeyringInfoInputView { } } } + + // MARK: - 최근 사용 템플릿 업데이트 + /// 새 템플릿을 맨 앞에 추가하고, 중복 제거 후 최대 10개 유지 + private func updateRecentTemplates(uid: String, templateId: String) { + let userRef = db.collection("User").document(uid) + + userRef.getDocument { snapshot, error in + guard let data = snapshot?.data(), + error == nil else { + print("[RecentTemplates] 문서 읽기 실패: \(error?.localizedDescription ?? "")") + return + } + + var recentTemplates = data["recentTemplates"] as? [String] ?? [] + + // 1. 이미 있으면 제거 (중복 방지) + recentTemplates.removeAll { $0 == templateId } + + // 2. 맨 앞에 추가 + recentTemplates.insert(templateId, at: 0) + + // 3. 최대 5개 유지 + if recentTemplates.count > 5 { + recentTemplates = Array(recentTemplates.prefix(5)) + } + + // 4. Firebase 업데이트 + userRef.updateData([ + "recentTemplates": recentTemplates + ]) { error in + if let error = error { + print("[RecentTemplates] 업데이트 실패: \(error.localizedDescription)") + } else { + print("[RecentTemplates] 업데이트 성공: \(templateId)") + } + } + } + } // MARK: - 위젯용 이미지 캡처 및 캐싱 private func captureAndCacheKeyring( diff --git a/Keychy/Keychy/Presentation/Workshop/ViewModels/WorkshopViewModel.swift b/Keychy/Keychy/Presentation/Workshop/ViewModels/WorkshopViewModel.swift index c2dd4dec0..c8603b576 100644 --- a/Keychy/Keychy/Presentation/Workshop/ViewModels/WorkshopViewModel.swift +++ b/Keychy/Keychy/Presentation/Workshop/ViewModels/WorkshopViewModel.swift @@ -173,6 +173,15 @@ class WorkshopViewModel { } } + /// 최근 사용 템플릿 (최신순, 최대 5개) + var recentTemplates: [KeyringTemplate] { + guard let user = userManager.currentUser else { return [] } + // recentTemplates ID 순서대로 템플릿 객체 반환 + return user.recentTemplates.compactMap { templateId in + templates.first { $0.id == templateId } + } + } + private var userManager: UserManager init(userManager: UserManager) { diff --git a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopMakingKeyringSection.swift b/Keychy/Keychy/Presentation/Workshop/Views/Bundle/WorkshopBundleBanner.swift similarity index 96% rename from Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopMakingKeyringSection.swift rename to Keychy/Keychy/Presentation/Workshop/Views/Bundle/WorkshopBundleBanner.swift index abfd05b43..0a3213689 100644 --- a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopMakingKeyringSection.swift +++ b/Keychy/Keychy/Presentation/Workshop/Views/Bundle/WorkshopBundleBanner.swift @@ -11,7 +11,7 @@ import NukeUI // MARK: - MakingKeyring Section extension WorkshopView { - var makingKeyringSection: some View { + var WorkshopBundleBanner: some View { VStack(spacing: 0) { // 제목 Text("내 마음대로 고르는\n다양한 템플릿(๑' ᵕ '๑)⸝*") @@ -72,8 +72,6 @@ extension WorkshopView { .resizable() .scaledToFit() } - - // TODO: 네트워크 연결 끊김 처리 } .frame(height: 120) .padding(.bottom, 10) diff --git a/Keychy/Keychy/Presentation/Workshop/Components/WorkshopComponents.swift b/Keychy/Keychy/Presentation/Workshop/Views/Components/WorkshopComponents.swift similarity index 100% rename from Keychy/Keychy/Presentation/Workshop/Components/WorkshopComponents.swift rename to Keychy/Keychy/Presentation/Workshop/Views/Components/WorkshopComponents.swift diff --git a/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopRecentTemplate.swift b/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopRecentTemplate.swift new file mode 100644 index 000000000..d8c10a4d7 --- /dev/null +++ b/Keychy/Keychy/Presentation/Workshop/Views/Keyring/WorkshopRecentTemplate.swift @@ -0,0 +1,148 @@ +// +// WorkshopRecentTemplate.swift +// Keychy +// +// Created by 길지훈 on 1/21/26. +// + +import SwiftUI +import NukeUI + +// MARK: - 최근 사용 템플릿 섹션 + +struct WorkshopRecentTemplate: View { + let templates: [KeyringTemplate] + let isLoading: Bool + var onTemplateTap: ((KeyringTemplate) -> Void)? + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + // 섹션 타이틀 + Text("최근 사용 템플릿") + .typography(.suit17B) + .foregroundColor(.black100) + .padding(.horizontal, 20) + + // 콘텐츠 + if isLoading { + loadingView + } else if templates.isEmpty { + recentEmptyView + } else { + templateScrollView + } + } + } + + // MARK: - 로딩 뷰 + + private var loadingView: some View { + HStack(spacing: 12) { + ForEach(0..<3, id: \.self) { _ in + SkeletonBox(width: 112, height: 112) + } + } + .padding(.horizontal, 20) + } + + // MARK: - 빈 상태 뷰 + private var recentEmptyView: some View { + HStack { + Spacer() + Text("키링을 만들면 최근 사용한 템플릿이 이곳에 표시됩니다") + .typography(.suit14R18) + .foregroundColor(.gray500) + .multilineTextAlignment(.center) + Spacer() + } + .frame(height: 100) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.white70) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.gray50, lineWidth: 1) + ) + .padding(.horizontal, 20) + } + + // MARK: - 템플릿 스크롤 뷰 + + private var templateScrollView: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 7) { + ForEach(templates, id: \.id) { template in + RecentTemplateCard(template: template) { + onTemplateTap?(template) + } + } + } + .padding(.horizontal, 20) + } + } +} + +// MARK: - 개별 템플릿 카드 +private struct RecentTemplateCard: View { + let template: KeyringTemplate + let onTap: () -> Void + + var body: some View { + Button(action: onTap) { + ZStack { + LazyImage(url: URL(string: template.thumbnailURL)) { state in + if let image = state.image { + image + .resizable() + .aspectRatio(contentMode: .fit) + } else if state.isLoading { + LoadingAlert(type: .short30, message: nil) + } else { + Color.gray50 + .frame(width: 112, height: 112) + } + } + .padding(5) + + // 유료 아이콘 + if !template.isFree { + VStack { + HStack { + Image(.myCoinMini) + + Spacer() + } + .padding(.top, 7) + .padding(.leading, 7) + Spacer() + } + } + } + .frame(width: 112, height: 112) + .background(Color.white) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .strokeBorder(.gray50, lineWidth: 1) + ) + } + .buttonStyle(.plain) + } +} + +// MARK: - Preview + +#Preview("로딩 상태") { + WorkshopRecentTemplate( + templates: [], + isLoading: true + ) +} + +#Preview("빈 상태") { + WorkshopRecentTemplate( + templates: [], + isLoading: false + ) +} diff --git a/Keychy/Keychy/Presentation/Workshop/Views/MyItemsView.swift b/Keychy/Keychy/Presentation/Workshop/Views/Main/MyItemsView.swift similarity index 100% rename from Keychy/Keychy/Presentation/Workshop/Views/MyItemsView.swift rename to Keychy/Keychy/Presentation/Workshop/Views/Main/MyItemsView.swift diff --git a/Keychy/Keychy/Presentation/Workshop/Views/WorkshopPreview.swift b/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopPreview.swift similarity index 100% rename from Keychy/Keychy/Presentation/Workshop/Views/WorkshopPreview.swift rename to Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopPreview.swift diff --git a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopTopBannerSection.swift b/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopTopBannerSection.swift index d542e7432..378b10de3 100644 --- a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopTopBannerSection.swift +++ b/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopTopBannerSection.swift @@ -20,7 +20,7 @@ extension WorkshopView { .padding(.horizontal, 20) .frame(maxWidth: .infinity) } - + /// 스크롤 시 나타나는 상단 타이틀 바 var topTitleBar: some View { HStack { @@ -34,14 +34,30 @@ extension WorkshopView { .background(Color.white100) .opacity(viewModel.mainContentOffset - 80 < 70 ? 1 : 0) } - + /// 타이틀 뷰 var titleView: some View { - Text("공방") - .typography(.nanum32EB) - .frame(maxWidth: .infinity, alignment: .leading) + HStack(spacing: 10) { + Button { + // TODO: - 공방 탭 액션 + workshopToggle = true + } label: { + Text("키링") + .typography(.nanum24EB) + .foregroundStyle(workshopToggle ? .black100 : .gray100) + } + + Button { + // TODO: - 뭉치 탭 액션 + workshopToggle = false + } label: { + Text("뭉치") + .typography(.nanum24EB) + .foregroundStyle(workshopToggle ? .gray100 : .black100) + } + } } - + /// 내 아이템 버튼 var myItemBtn: some View { Button { @@ -51,9 +67,9 @@ extension WorkshopView { Image(.myItem) .resizable() .scaledToFit() - + Spacer() - + Text("내 아이템") .typography(.suit17B) .foregroundColor(.black) diff --git a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopView.swift b/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopView.swift index adf5ba17a..7397d42d9 100644 --- a/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopView.swift +++ b/Keychy/Keychy/Presentation/Workshop/Views/Main/WorkshopView.swift @@ -17,6 +17,7 @@ struct WorkshopView: View { @State var viewModel: WorkshopViewModel @State private var hasInitialized = false @State private var isTabBarVisible = true + @State var workshopToggle: Bool = true let categories = ["템플릿", "카라비너", "이펙트", "배경"] @@ -63,7 +64,7 @@ struct WorkshopView: View { .allowsHitTesting(false) } .background( - Image(.back) + Image(.workshopKeyringBGB) .resizable() .scaledToFill() ) @@ -115,14 +116,15 @@ struct WorkshopView: View { VStack(spacing: 0) { // 상단 배너 (코인 버튼 + 타이틀) topBannerSection - + Spacer() - .frame(height: 64) - - makingKeyringSection - + .frame(height: 106) + + // 최근 사용 템플릿 + recentTemplateSection + Spacer() - .frame(height: 14) + .frame(height: 43) // 메인 콘텐츠 (그리드) mainContentSection @@ -141,7 +143,7 @@ struct WorkshopView: View { } .padding(.top, 60) .background(alignment: .top) { - Image(.workshopBG) + Image(.workshopKeyringBGF) .resizable() .aspectRatio(contentMode: .fit) } @@ -164,6 +166,21 @@ extension WorkshopView { } } +// MARK: - Recent Template Section + +extension WorkshopView { + /// 최근 사용 템플릿 섹션 + var recentTemplateSection: some View { + WorkshopRecentTemplate( + templates: viewModel.recentTemplates, + isLoading: viewModel.isLoading + ) { template in + // 템플릿 상세 프리뷰로 이동 + router.push(.workshopPreview(item: template)) + } + } +} + // MARK: - Network Error extension WorkshopView { diff --git a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/WorkshopBack.pdf b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/WorkshopBack.pdf deleted file mode 100644 index 94e825e5d..000000000 Binary files a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/WorkshopBack.pdf and /dev/null differ diff --git a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/Contents.json b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/Contents.json similarity index 73% rename from Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/Contents.json rename to Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/Contents.json index cf3c0409f..42b29f7b3 100644 --- a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/Contents.json +++ b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "back.pdf", + "filename" : "workshopKeyringBGB.pdf", "idiom" : "universal" } ], diff --git a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/back.pdf b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/workshopKeyringBGB.pdf similarity index 94% rename from Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/back.pdf rename to Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/workshopKeyringBGB.pdf index a8f977d74..3db1ffd95 100644 Binary files a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/back.imageset/back.pdf and b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGB.imageset/workshopKeyringBGB.pdf differ diff --git a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/Contents.json b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/Contents.json similarity index 73% rename from Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/Contents.json rename to Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/Contents.json index a54c85a93..e96ceaa6d 100644 --- a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopBG.imageset/Contents.json +++ b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "WorkshopBack.pdf", + "filename" : "workshopKeyringBGF.pdf", "idiom" : "universal" } ], diff --git a/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/workshopKeyringBGF.pdf b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/workshopKeyringBGF.pdf new file mode 100644 index 000000000..a226b31d6 Binary files /dev/null and b/Keychy/Keychy/Resources/Assets.xcassets/02. WorkShop/workshopKeyringBGF.imageset/workshopKeyringBGF.pdf differ