From 83e670d3912b5586f9fdd3ecf0264291082672d9 Mon Sep 17 00:00:00 2001 From: eunjoo-choi Date: Sun, 14 Apr 2024 22:31:12 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EC=9D=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=9C=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=ED=99=94=EB=A9=B4=EC=97=90=20=EC=A7=84?= =?UTF-8?q?=EC=9E=85=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B2=84=ED=8A=BC=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 삭제 버튼은 texteditor에 포커스가 없는 경우에만 (키보드 hide) 노출됨 delete 코드 추가 및 테스트 코드 작성 --- .../Localization/Localizable.xcstrings | 16 +++++++++++ .../MoodRecord/MoodRecordView.swift | 25 +++++++++++++++++ .../MoodRecord/MoodRecordViewModel.swift | 27 +++++++++++++++++++ .../MoodRecordViewModelTests.swift | 24 +++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/PlantingMind/PlantingMind/Localization/Localizable.xcstrings b/PlantingMind/PlantingMind/Localization/Localizable.xcstrings index e679815..f567580 100644 --- a/PlantingMind/PlantingMind/Localization/Localizable.xcstrings +++ b/PlantingMind/PlantingMind/Localization/Localizable.xcstrings @@ -161,6 +161,22 @@ } } }, + "delete_alert" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deleted record cannot be recovered. Are you sure you want to delete it?" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "삭제된 기록은 복구할 수 없습니다. 그래도 삭제하시겠습니까?" + } + } + } + }, "done" : { "extractionState" : "manual", "localizations" : { diff --git a/PlantingMind/PlantingMind/MoodRecord/MoodRecordView.swift b/PlantingMind/PlantingMind/MoodRecord/MoodRecordView.swift index 71e6ffd..c78f506 100644 --- a/PlantingMind/PlantingMind/MoodRecord/MoodRecordView.swift +++ b/PlantingMind/PlantingMind/MoodRecord/MoodRecordView.swift @@ -10,9 +10,11 @@ import SwiftUI struct MoodRecordView: View { @Environment(\.dismiss) var dismiss @Environment(\.colorScheme) var colorScheme + @FocusState var isFocused: Bool @ObservedObject var viewModel: MoodRecordViewModel @State var isDialogPresent: Bool = false + @State var isDeleteAlertPresent: Bool = false var body: some View { NavigationStack() { @@ -44,6 +46,19 @@ struct MoodRecordView: View { limitStringView } + if isFocused == false, viewModel.isFirstRecord == false { + Button { + isDeleteAlertPresent.toggle() + } label: { + Image(systemName: "trash.fill") + .foregroundStyle(.white) + .padding() + .padding(.horizontal, 10) + .background(.red) + .clipShape(Capsule(style: .continuous)) + } + } + Spacer() } } @@ -76,6 +91,15 @@ struct MoodRecordView: View { } .foregroundStyle(Color.Custom.general) } + .alert("delete_alert", isPresented: $isDeleteAlertPresent, actions: { + Button("cancel", role: .cancel) { } + Button("ok", role: .destructive) { + viewModel.deleteRecord(completionHandler: {result in + guard result else { return } + dismiss() + }) + } + }) .alert("error_description", isPresented: $viewModel.showErrorAlert) { Button("ok", role: .cancel) { } } @@ -109,6 +133,7 @@ struct MoodRecordView: View { .opacity(0.8) .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) .padding(.horizontal) + .focused($isFocused) .onChange(of: viewModel.reason) { _ in if viewModel.reason.count > 100 { viewModel.reason.removeLast() diff --git a/PlantingMind/PlantingMind/MoodRecord/MoodRecordViewModel.swift b/PlantingMind/PlantingMind/MoodRecord/MoodRecordViewModel.swift index 0cb345b..782b4e5 100644 --- a/PlantingMind/PlantingMind/MoodRecord/MoodRecordViewModel.swift +++ b/PlantingMind/PlantingMind/MoodRecord/MoodRecordViewModel.swift @@ -15,6 +15,7 @@ class MoodRecordViewModel: ObservableObject { private let originalReason: String let date: Date + let isFirstRecord: Bool @Published var mood: Mood @Published var reason: String @@ -28,6 +29,8 @@ class MoodRecordViewModel: ObservableObject { init(context: NSManagedObjectContext, calendarModel: CalendarModel, moodRecord: MoodRecord?) { self.context = context + self.isFirstRecord = moodRecord == nil + self.date = Calendar.current.date(from: DateComponents(year: calendarModel.year, month: calendarModel.month, day: calendarModel.day)) ?? Date() @@ -47,6 +50,30 @@ class MoodRecordViewModel: ObservableObject { return false } + func deleteRecord(completionHandler: (Bool) -> Void) { + let fetchRequest = NSFetchRequest(entityName: "MoodRecord") + let predicate = NSPredicate(format: "timestamp == %@", date as NSDate) + fetchRequest.predicate = predicate + + do { + guard let result = try self.context.fetch(fetchRequest).first else { + completionHandler(false) + return + } + + context.delete(result) + try context.save() + WidgetCenter.shared.reloadAllTimelines() + + self.sendFetchNotification() + completionHandler(true) + } catch { + self.showErrorAlert.toggle() + CrashlyticsLog.shared.record(error: error) + completionHandler(false) + } + } + func save() { let fetchRequest = NSFetchRequest(entityName: "MoodRecord") let predicate = NSPredicate(format: "timestamp == %@", date as NSDate) diff --git a/PlantingMind/PlantingMindTests/MoodRecordViewModelTests.swift b/PlantingMind/PlantingMindTests/MoodRecordViewModelTests.swift index 5d43d5a..ba6d234 100644 --- a/PlantingMind/PlantingMindTests/MoodRecordViewModelTests.swift +++ b/PlantingMind/PlantingMindTests/MoodRecordViewModelTests.swift @@ -35,6 +35,7 @@ final class MoodRecordViewModelTests: XCTestCase { XCTAssertEqual(viewModel.mood, expectedMood) XCTAssertEqual(viewModel.reason, expectedReason) + XCTAssertTrue(viewModel.isFirstRecord) } func test_기분_데이터_넘겨주는_경우() throws { @@ -79,6 +80,29 @@ final class MoodRecordViewModelTests: XCTestCase { XCTAssertEqual(record?.reason, expectedReason) } + func test_기록_삭제_확인() throws { + let moodRecord = MoodRecord(context: coreDataStack.persistentContainer.viewContext) + moodRecord.mood = Mood.nice.rawValue + moodRecord.reason = "reason reason" + + let viewModel = MoodRecordViewModel(context: coreDataStack.persistentContainer.viewContext, + calendarModel: calendarModel, + moodRecord: moodRecord) + + viewModel.deleteRecord { result in + // 기록 저장안한 경우 false 리턴 + XCTAssertFalse(result) + } + + viewModel.save() + viewModel.deleteRecord { result in + XCTAssertTrue(result) + } + + let record = try self.fetch() + XCTAssertNil(record) + } + func test_취소했을_때_변경사항_없는_경우() throws { let moodRecord = MoodRecord(context: coreDataStack.persistentContainer.viewContext) moodRecord.mood = Mood.nice.rawValue