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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions Keychy/Keychy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
38D17A512EBBF88C00F52A88 /* CollectionViewModel+Edit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D17A502EBBF88C00F52A88 /* CollectionViewModel+Edit.swift */; };
38DD90622ED594C00042EB45 /* FestivalKeyringContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DD90612ED594C00042EB45 /* FestivalKeyringContextMenu.swift */; };
38DD909C2EF1679D0042EB45 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DD909B2EF1679D0042EB45 /* Color+Extension.swift */; };
38DE8C432F38ECEF00C87924 /* BundleViewModel+Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DE8C422F38ECEF00C87924 /* BundleViewModel+Filter.swift */; };
38DE8C452F38FAA700C87924 /* BundleViewModel+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DE8C442F38FAA700C87924 /* BundleViewModel+Sort.swift */; };
38F832CB2EC9067300D3A248 /* WidgetOnboardingStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F832CA2EC9067300D3A248 /* WidgetOnboardingStepView.swift */; };
38F832CD2EC90DEF00D3A248 /* WidgetOnboardingStepView+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F832CC2EC90DEF00D3A248 /* WidgetOnboardingStepView+Helpers.swift */; };
38F832CF2EC914C900D3A248 /* InvenExpandPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F832CE2EC914C900D3A248 /* InvenExpandPopup.swift */; };
Expand Down Expand Up @@ -359,9 +361,6 @@
AA2146B72F15E5B60048D40E /* BundleEditView+SelectSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2146B62F15E5B60048D40E /* BundleEditView+SelectSheet.swift */; };
AA2146BB2F161D0C0048D40E /* BundleEditView+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2146BA2F161D0C0048D40E /* BundleEditView+Initialization.swift */; };
AA3908F82EC8BF0400D87EEC /* BundleDetailView+SaveImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3908F72EC8BF0400D87EEC /* BundleDetailView+SaveImage.swift */; };
BC4CMPLT2F3B123400000001 /* BundleCompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */; };
BC5CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */; };
BC6CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */; };
AA3909462EC9F29500D87EEC /* UIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3909452EC9F29500D87EEC /* UIApplication+Extension.swift */; };
AA39098E2ECA061700D87EEC /* GridItemSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA39098D2ECA061700D87EEC /* GridItemSpacing.swift */; };
AA390CE52ECC60A700D87EEC /* BundleRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA390CE42ECC60A700D87EEC /* BundleRoute.swift */; };
Expand Down Expand Up @@ -396,6 +395,9 @@
BC0002102F35F00200000004 /* KeyringEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC00020E2F35F00200000002 /* KeyringEmptyStateView.swift */; };
BC0002132F35F00200000006 /* KeyringSelectionContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0002112F35F00200000005 /* KeyringSelectionContent.swift */; };
BC0002152F35F00200000008 /* BundleKeyringCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0002142F35F00200000007 /* BundleKeyringCellView.swift */; };
BC4CMPLT2F3B123400000001 /* BundleCompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */; };
BC5CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */; };
BC6CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */; };
C645AE9F2EB1055C004BFE69 /* CategoryTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C645AE9E2EB1055C004BFE69 /* CategoryTabBar.swift */; };
C645AEA32EB1B8FC004BFE69 /* DataInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C645AEA22EB1B8FC004BFE69 /* DataInitializer.swift */; };
C665DDE82EAEFA8700CE4495 /* CoinChargeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C665DDE72EAEFA8700CE4495 /* CoinChargeView.swift */; };
Expand Down Expand Up @@ -548,6 +550,8 @@
38D17A502EBBF88C00F52A88 /* CollectionViewModel+Edit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewModel+Edit.swift"; sourceTree = "<group>"; };
38DD90612ED594C00042EB45 /* FestivalKeyringContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FestivalKeyringContextMenu.swift; sourceTree = "<group>"; };
38DD909B2EF1679D0042EB45 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = "<group>"; };
38DE8C422F38ECEF00C87924 /* BundleViewModel+Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleViewModel+Filter.swift"; sourceTree = "<group>"; };
38DE8C442F38FAA700C87924 /* BundleViewModel+Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleViewModel+Sort.swift"; sourceTree = "<group>"; };
38F832CA2EC9067300D3A248 /* WidgetOnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetOnboardingStepView.swift; sourceTree = "<group>"; };
38F832CC2EC90DEF00D3A248 /* WidgetOnboardingStepView+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetOnboardingStepView+Helpers.swift"; sourceTree = "<group>"; };
38F832CE2EC914C900D3A248 /* InvenExpandPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvenExpandPopup.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -822,9 +826,6 @@
AA2146B62F15E5B60048D40E /* BundleEditView+SelectSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleEditView+SelectSheet.swift"; sourceTree = "<group>"; };
AA2146BA2F161D0C0048D40E /* BundleEditView+Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleEditView+Initialization.swift"; sourceTree = "<group>"; };
AA3908F72EC8BF0400D87EEC /* BundleDetailView+SaveImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleDetailView+SaveImage.swift"; sourceTree = "<group>"; };
BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleCompleteView.swift; sourceTree = "<group>"; };
BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleCompleteView+VideoGen.swift"; sourceTree = "<group>"; };
BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleCompleteView+SaveImage.swift"; sourceTree = "<group>"; };
AA3909452EC9F29500D87EEC /* UIApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extension.swift"; sourceTree = "<group>"; };
AA39098D2ECA061700D87EEC /* GridItemSpacing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridItemSpacing.swift; sourceTree = "<group>"; };
AA390CE42ECC60A700D87EEC /* BundleRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleRoute.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -859,6 +860,9 @@
BC00020E2F35F00200000002 /* KeyringEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringEmptyStateView.swift; sourceTree = "<group>"; };
BC0002112F35F00200000005 /* KeyringSelectionContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyringSelectionContent.swift; sourceTree = "<group>"; };
BC0002142F35F00200000007 /* BundleKeyringCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleKeyringCellView.swift; sourceTree = "<group>"; };
BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleCompleteView.swift; sourceTree = "<group>"; };
BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleCompleteView+VideoGen.swift"; sourceTree = "<group>"; };
BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BundleCompleteView+SaveImage.swift"; sourceTree = "<group>"; };
C645AE9E2EB1055C004BFE69 /* CategoryTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryTabBar.swift; sourceTree = "<group>"; };
C645AEA22EB1B8FC004BFE69 /* DataInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataInitializer.swift; sourceTree = "<group>"; };
C665DDE72EAEFA8700CE4495 /* CoinChargeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinChargeView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2172,6 +2176,8 @@
61SOUIL06G2NM0OODQ5MLB0A /* BundleViewModel+Fetch.swift */,
AA69DD232F14C56F00C0A41C /* BundleViewModel+CRUD.swift */,
AA69DD252F14C60000C0A41C /* BundleViewModel+Edit.swift */,
38DE8C442F38FAA700C87924 /* BundleViewModel+Sort.swift */,
38DE8C422F38ECEF00C87924 /* BundleViewModel+Filter.swift */,
QM5GFF2WHZX9U24OOWPTMM4S /* BundleViewModel+Purchase.swift */,
40QZ1H4Y8EH2YZZUOT7WN7MX /* BundleViewModel+Helpers.swift */,
DTQCH5OWZZJ8N03KVZERHJKR /* BundleViewModel+Cache.swift */,
Expand Down Expand Up @@ -2227,6 +2233,16 @@
path = Edit;
sourceTree = "<group>";
};
BC0CMPLT2F3B123400000000 /* Complete */ = {
isa = PBXGroup;
children = (
BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */,
BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */,
BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */,
);
path = Complete;
sourceTree = "<group>";
};
C665DDE92EAEFAA800CE4495 /* Coin */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2321,16 +2337,6 @@
path = Detail;
sourceTree = "<group>";
};
BC0CMPLT2F3B123400000000 /* Complete */ = {
isa = PBXGroup;
children = (
BC1CMPLT2F3B123400000001 /* BundleCompleteView.swift */,
BC2CMPLT2F3B123400000002 /* BundleCompleteView+VideoGen.swift */,
BC3CMPLT2F3B123400000003 /* BundleCompleteView+SaveImage.swift */,
);
path = Complete;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -2670,6 +2676,7 @@
4C4733BF2F1FA388005D2376 /* PolaroidVM.swift in Sources */,
4C4733C02F1FA388005D2376 /* ClearSketchPreview.swift in Sources */,
4C4733C12F1FA388005D2376 /* ClearSketchVM+Drawing.swift in Sources */,
38DE8C452F38FAA700C87924 /* BundleViewModel+Sort.swift in Sources */,
4C4733C22F1FA388005D2376 /* PixelVM+Effect.swift in Sources */,
4C4733C32F1FA388005D2376 /* CropModels.swift in Sources */,
4C4733C42F1FA388005D2376 /* SpeechBubbleVM+Effect.swift in Sources */,
Expand Down Expand Up @@ -2857,6 +2864,7 @@
BC00010B2F35F0010000000B /* BundleCreateView+Capture.swift in Sources */,
BC00010C2F35F0010000000C /* BundleEditView+Capture.swift in Sources */,
38C3C2922EC1F787003C5DE1 /* CollectionKeyringDetailView+Lifecycle.swift in Sources */,
38DE8C432F38ECEF00C87924 /* BundleViewModel+Filter.swift in Sources */,
4C8426642ED375840050B6FE /* ColorPalette.swift in Sources */,
C6C402A32EB40ACA006B58DF /* AlarmView.swift in Sources */,
C6B56F0B2EBC43AC0049F969 /* StoreProduct.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Keychy/Keychy/Core/Navigation/Routes/BundleRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ protocol BundleRoute: Hashable {
static var bundleEditView: Self { get }
static var bundleCompleteView: Self { get }
static var coinCharge: Self { get }
static var widgetSettingView: Self { get }
}
3 changes: 3 additions & 0 deletions Keychy/Keychy/Core/Navigation/Routes/HomeRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ enum HomeRoute: Hashable, BundleRoute {
case bundleNameEditView
case bundleEditView
case bundleCompleteView

// Widget
case widgetSettingView

// Home
case coinCharge
Expand Down
3 changes: 3 additions & 0 deletions Keychy/Keychy/Core/Navigation/Routes/WorkshopRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ enum WorkshopRoute: Hashable, BundleRoute {
// MARK: - 선물 포장 완료
case packageComplete(keyringDocumentId: String, postOfficeId: String, templateId: String, shareLink: String)

// MARK: - 위젯 가이딩
case widgetSettingView

/// template.id 문자열을 WorkshopRoute로 변환
static func from(string: String) -> WorkshopRoute? {
switch string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// BundleViewModel+Filter.swift
// Keychy
//
// Created by Jini on 2/9/26.
//

import SwiftUI

extension BundleViewModel {
// MARK: - 검색 키워드 필터링
/// 검색어로 뭉치 필터링 (이름 기준)
func getFilteredBundles(searchText: String = "") -> [KeyringBundle] {
var result = bundles

// 검색 필터
if !searchText.isEmpty {
result = result.filter { bundle in
bundle.name.localizedCaseInsensitiveContains(searchText)
}
}

// 정렬 적용
return sortBundles(result)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// BundleViewModel+Sort.swift
// Keychy
//
// Created by Jini on 2/9/26.
//

import SwiftUI

extension BundleViewModel {
// MARK: - 정렬 방식

/// 정렬 기준 변경 및 즉시 적용
func updateSortOrder(_ newSort: String) {
selectedSort = newSort
}

/// 뭉치 배열을 정렬 (메인 뭉치는 항상 최상단 유지)
func sortBundles(_ bundles: [KeyringBundle]) -> [KeyringBundle] {
// 메인 뭉치와 일반 뭉치 분리
let mainBundles = bundles.filter { $0.isMain }
let normalBundles = bundles.filter { !$0.isMain }

// 일반 뭉치를 선택된 정렬 기준으로 정렬
let sortedNormalBundles: [KeyringBundle]

switch selectedSort {
case "최신순":
sortedNormalBundles = normalBundles.sorted { $0.createdAt > $1.createdAt }
case "오래된순":
sortedNormalBundles = normalBundles.sorted { $0.createdAt < $1.createdAt }
case "이름순":
sortedNormalBundles = normalBundles.sorted {
$0.name.localizedStandardCompare($1.name) == .orderedAscending
}
default:
sortedNormalBundles = normalBundles.sorted { $0.createdAt > $1.createdAt }
}

// 메인 뭉치(최신순 정렬) + 정렬된 일반 뭉치
return mainBundles.sorted { $0.createdAt > $1.createdAt } + sortedNormalBundles
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class BundleViewModel {
var isLoading = false
var isPurchasing = false

// MARK: - 정렬 상태

var selectedSort: String = "최신순" // 기본값

// MARK: - 시트 필터/정렬 상태

var sheetSortOrder: String = "최신순"
Expand Down Expand Up @@ -150,12 +154,7 @@ class BundleViewModel {
// MARK: - 정렬된 뭉치

var sortedBundles: [KeyringBundle] {
bundles.sorted { a, b in
if a.isMain != b.isMain {
return a.isMain
}
return a.createdAt > b.createdAt
}
sortBundles(bundles)
}

// MARK: - 구성 ID 저장소 (편집 → 상세 화면 전환용)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ extension BundleDetailView {
uiState.showDeleteAlert = true
}
},
onWidget: {
uiState.showMenu = false

withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
router.push(.widgetSettingView)
}
},
isMain: bundle.isMain
)
.zIndex(50)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,54 @@ extension BundleDetailView {
return nil
}
}

/// 공유용 영상 생성
@MainActor
func generateVideoForShare() async {
guard !uiState.isGeneratingVideo else { return }

// 이미 캐시된 영상이 있으면 바로 공유
if cachedVideoURL != nil {
showShareSheet = true
return
}

uiState.isGeneratingVideo = true

guard let bundle = bundleVM.selectedBundle,
let background = bundleVM.selectedBackground,
let carabiner = bundleVM.selectedCarabiner else {
uiState.isGeneratingVideo = false
return
}

do {
// 배경 이미지 로드
let backgroundImage = await loadImage(from: background.backgroundImage)

// 영상 생성 (기존 generateVideo 함수 사용)
let videoURL = try await videoGenerator.generateVideo(
keyringDataList: keyringDataList,
backgroundImage: backgroundImage,
backgroundImageURL: background.backgroundImage,
carabinerBackImageURL: carabiner.backImageURL,
carabinerFrontImageURL: carabiner.frontImageURL,
carabinerX: carabiner.carabinerX,
carabinerY: carabiner.carabinerY,
carabinerWidth: carabiner.carabinerWidth,
carabinerType: carabiner.type,
bundleScale: 2.5
)

cachedVideoURL = videoURL
uiState.isGeneratingVideo = false

// 영상 생성 완료 후 공유 시트 표시
showShareSheet = true

} catch {
print("[BundleDetail] 영상 생성 실패: \(error.localizedDescription)")
uiState.isGeneratingVideo = false
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금보니 제가 디테일뷰,완성뷰에 둘다 만들어뒀네요
후에 BundleViewModel+VideoGen으로 통합해도 될듯합니당

Loading