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
23 changes: 23 additions & 0 deletions GAMSS/Resources/Assets.xcassets/noteStrip.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "note_strip.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "note_strip@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "note_strip@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion GAMSS/Sources/Core/DateFormatterFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ enum DateFormatterFactory {
return formatter
}

/// `yyyy-MM-dd`
static var dateWithHypen: DateFormatter {
let formatter = dateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}

/// `yyyy-MM`
static var dateWithHyphen: DateFormatter {
static var yearMonthWithHypen: DateFormatter {
let formatter = dateFormatter()
formatter.dateFormat = "yyyy-MM"
return formatter
Expand Down
4 changes: 2 additions & 2 deletions GAMSS/Sources/Data/Repository/DefaultCardRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ final class DefaultCardRepository: CardRepository {
}

func fetchCardsByDate(yearMonth: Date, emotion: Emotion) async throws -> [DailyEmotion] {
let yearMonth = DateFormatterFactory.dateWithHyphen.string(from: yearMonth)
let yearMonth = DateFormatterFactory.yearMonthWithHypen.string(from: yearMonth)
let response = try await networkManager.request(
CardEndpoint.fetchCardsByDate(yearMonth: yearMonth, emotion: emotion.rawValue),
responseType: APIResponse<[FetchCardsByDateResponseDTO]>.self
)
return response.data.compactMap { response in
guard let date = DateFormatterFactory.dateWithDot.date(from: response.date) else {
guard let date = DateFormatterFactory.dateWithHypen.date(from: response.date) else {
return nil
}

Expand Down
44 changes: 34 additions & 10 deletions GAMSS/Sources/Presentation/Archive/Detail/ArchiveDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct ArchiveDetailView: View {
@State private var scene = DropStackScene()
@State private var isMonthPickerPresented = false
@State private var isDeleteAllModalPresented = false
@State private var selectedNote: DropNote?

private let title: String

Expand All @@ -39,16 +40,25 @@ struct ArchiveDetailView: View {
ZStack {
Color.colorWhite

GeometryReader { proxy in
SpriteView(scene: scene, options: [.allowsTransparency])
.onAppear {
scene.scaleMode = .resizeFill
scene.updateSize(proxy.size)
scene.render(notes: viewModel.notes)
}
.onChange(of: proxy.size) { _, newSize in
scene.updateSize(newSize)
}
if viewModel.notes.isEmpty, !viewModel.isLoading {
Text("아직 남겨둔 이야기가 없어요.")
.typography(.body3Regular)
.foregroundStyle(Color.colorGray500)
} else {
GeometryReader { proxy in
SpriteView(scene: scene, options: [.allowsTransparency])
.onAppear {
scene.scaleMode = .resizeFill
scene.updateSize(proxy.size)
scene.onSelectNote = { note in
selectedNote = note
}
scene.render(notes: viewModel.notes)
}
.onChange(of: proxy.size) { _, newSize in
scene.updateSize(newSize)
}
}
}
}
}
Expand Down Expand Up @@ -82,6 +92,20 @@ struct ArchiveDetailView: View {
}
}
}
.fullScreenCover(item: $selectedNote) { note in
CardDetailView(
viewModel: CardDetailViewModel(
cardId: note.id,
getCardUseCase: GetCardUseCase(
cardRepository: DefaultCardRepository(networkManager: NetworkManager.shared)
)
),
onClose: {
selectedNote = nil
}
)
.presentationBackground(.clear)
}
if isDeleteAllModalPresented {
ModalContainerView(
isPresented: $isDeleteAllModalPresented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class ArchiveDetailViewModel: ObservableObject {

do {
notes = try await fetchCardsByDateUseCase.execute(yearMonth: selectedMonth).map { dailyEmotion in
DropNote(id: dailyEmotion.conversationId, imageName: "cardFoldStepTwo")
DropNote(id: dailyEmotion.id, imageName: "cardFoldStepTwo")
}
} catch {
notes = []
Expand All @@ -47,6 +47,7 @@ final class ArchiveDetailViewModel: ObservableObject {

func selectMonth(_ month: Date) async {
selectedMonth = month
notes = []
await load()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import SpriteKit

final class DropStackScene: SKScene {
var onSelectNote: ((DropNote) -> Void)?

private let floorNodeName = "drop-floor"
private let boxNodeName = "drop-note"
private let dropSize = CGSize(width: 80, height: 80)
private let floorHeight: CGFloat = 12

private var pendingNotes: [DropNote] = []
private var renderedNotes: [DropNote] = []
private var isAttachedToView = false

override func didMove(to view: SKView) {
Expand All @@ -39,25 +42,37 @@ final class DropStackScene: SKScene {
}

func render(notes: [DropNote]) {
clear()
pendingNotes = notes
flushPendingNotesIfNeeded()
}

func clear() {
removeAllActions()
pendingNotes = []
renderedNotes = []
children
.filter { $0.name == boxNodeName }
.forEach { $0.removeFromParent() }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: self),
let id = nodes(at: location).first(where: { $0.name == boxNodeName })?.userData?["id"] as? Int,
let note = renderedNotes.first(where: { $0.id == id })
else { return }

onSelectNote?(note)
}

private func flushPendingNotesIfNeeded() {
guard isAttachedToView, size.width > 0, size.height > 0, !pendingNotes.isEmpty else {
return
}

let notes = pendingNotes
pendingNotes = []
renderedNotes = notes

removeAllActions()
children
Expand All @@ -67,7 +82,7 @@ final class DropStackScene: SKScene {
for (index, note) in notes.enumerated() {
let wait = SKAction.wait(forDuration: 0.07 * Double(index))
let spawn = SKAction.run { [weak self] in
self?.spawnOne(imageName: note.imageName)
self?.spawnOne(note: note)
}
run(.sequence([wait, spawn]))
}
Expand All @@ -93,9 +108,10 @@ final class DropStackScene: SKScene {
addChild(floor)
}

private func spawnOne(imageName: String) {
let node = SKSpriteNode(imageNamed: imageName)
private func spawnOne(note: DropNote) {
let node = SKSpriteNode(imageNamed: note.imageName)
node.name = boxNodeName
node.userData = ["id": note.id]
node.size = dropSize

let minX = dropSize.width * 0.5
Expand Down
37 changes: 25 additions & 12 deletions GAMSS/Sources/Presentation/CardDetail/CardDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct CardDetailView: View {
@StateObject private var viewModel: CardDetailViewModel
@StateObject private var conversationHistoryViewModel: ConversationHistoryViewModel
@State private var isShowingConversation = false
@State private var isShredPresented = false

private let transitionDuration = 0.22

Expand Down Expand Up @@ -73,8 +74,6 @@ struct CardDetailView: View {
Task {
if isLoadFailure {
await viewModel.loadCard()
} else {
await discardCard()
}
}
}
Expand All @@ -84,13 +83,33 @@ struct CardDetailView: View {
Button("확인", role: .cancel) {}
}
}
.fullScreenCover(isPresented: $isShredPresented) {
if let card = viewModel.card {
CardShredView(
viewModel: CardShredViewModel(
cardId: card.id,
deleteCardUseCase: DeleteCardUseCase(
cardRepository: DefaultCardRepository(networkManager: NetworkManager.shared)
)
),
stripImageName: "noteStrip",
onBack: {
isShredPresented = false
},
onComplete: {
isShredPresented = false
onClose()
}
)
}
}
}

private var bottomActions: some View {
VStack(spacing: Spacing.spacing200) {
HStack(spacing: Spacing.spacing050) {
OutlineButton(title: "기록 버리기") {
Task { await discardCard() }
isShredPresented = true
}
.frame(width: 97)

Expand Down Expand Up @@ -135,21 +154,15 @@ struct CardDetailView: View {
isShowingConversation = false
}
}

private func discardCard() async {
let succeeded = await viewModel.deleteCard()
if succeeded {
onClose()
}
}
}

#Preview {
CardDetailView(
viewModel: CardDetailViewModel(
cardId: 1,
getCardUseCase: GetCardUseCase(cardRepository: DefaultCardRepository(networkManager: NetworkManager.shared)),
deleteCardUseCase: DeleteCardUseCase(cardRepository: DefaultCardRepository(networkManager: NetworkManager.shared))
getCardUseCase: GetCardUseCase(
cardRepository: DefaultCardRepository(networkManager: NetworkManager.shared)
)
),
onClose: {}
)
Expand Down
17 changes: 1 addition & 16 deletions GAMSS/Sources/Presentation/CardDetail/CardDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ final class CardDetailViewModel: ObservableObject {

private let cardId: Int
private let getCardUseCase: GetCardUseCase
private let deleteCardUseCase: DeleteCardUseCase

init(cardId: Int, getCardUseCase: GetCardUseCase, deleteCardUseCase: DeleteCardUseCase) {
init(cardId: Int, getCardUseCase: GetCardUseCase) {
self.cardId = cardId
self.getCardUseCase = getCardUseCase
self.deleteCardUseCase = deleteCardUseCase
}

/// 카드가 아직 없고(로딩 실패로 보여줄 게 없음) 알럿이 떠 있으면, 알럿의 "닫기"가 화면 자체를
Expand All @@ -39,17 +37,4 @@ final class CardDetailViewModel: ObservableObject {
alertMessage = "카드를 불러오지 못했어요"
}
}

@discardableResult
func deleteCard() async -> Bool {
isLoading = true
defer { isLoading = false }
do {
try await deleteCardUseCase.execute(cardId: cardId)
return true
} catch {
alertMessage = "카드를 삭제하지 못했어요"
return false
}
}
}
Loading
Loading