Skip to content

Commit 90de453

Browse files
committed
달력 하단에 기분 통계 추가
- 기록이 없는 경우는 문구를 넣음
1 parent f09ed83 commit 90de453

File tree

8 files changed

+193
-18
lines changed

8 files changed

+193
-18
lines changed

PlantingMind/PlantingMind.xcodeproj/project.pbxproj

+24
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
7B2CFC1D2B5A6FFB00641F6D /* CalendarGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2CFC1C2B5A6FFB00641F6D /* CalendarGridView.swift */; };
2727
7B34415D2BA1B87A00DA43F6 /* MonthPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B34415C2BA1B87A00DA43F6 /* MonthPickerViewModel.swift */; };
2828
7B34415F2BA1F85600DA43F6 /* MonthPickerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B34415E2BA1F85600DA43F6 /* MonthPickerViewModelTests.swift */; };
29+
7B4C07B62BB6B42E00B60CCD /* MoodAnalysisModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C07B52BB6B42E00B60CCD /* MoodAnalysisModel.swift */; };
30+
7B4C07B82BB6B6B200B60CCD /* AnalysisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C07B72BB6B6B200B60CCD /* AnalysisView.swift */; };
31+
7B4C07BC2BB6C1F200B60CCD /* AnalysisViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C07BB2BB6C1F200B60CCD /* AnalysisViewModelTests.swift */; };
32+
7B4C07BE2BB6C85D00B60CCD /* AnalysisViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C07BD2BB6C85D00B60CCD /* AnalysisViewModel.swift */; };
2933
7B571B552B95E5DA0019DEB8 /* MoodRecord+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B571B532B95E5DA0019DEB8 /* MoodRecord+CoreDataClass.swift */; };
3034
7B571B562B95E5DA0019DEB8 /* MoodRecord+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B571B542B95E5DA0019DEB8 /* MoodRecord+CoreDataProperties.swift */; };
3135
7B63CA9E2BA9CB4600943DED /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B63CA9D2BA9CB4600943DED /* WidgetKit.framework */; };
@@ -116,6 +120,10 @@
116120
7B2CFC1C2B5A6FFB00641F6D /* CalendarGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarGridView.swift; sourceTree = "<group>"; };
117121
7B34415C2BA1B87A00DA43F6 /* MonthPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthPickerViewModel.swift; sourceTree = "<group>"; };
118122
7B34415E2BA1F85600DA43F6 /* MonthPickerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthPickerViewModelTests.swift; sourceTree = "<group>"; };
123+
7B4C07B52BB6B42E00B60CCD /* MoodAnalysisModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodAnalysisModel.swift; sourceTree = "<group>"; };
124+
7B4C07B72BB6B6B200B60CCD /* AnalysisView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisView.swift; sourceTree = "<group>"; };
125+
7B4C07BB2BB6C1F200B60CCD /* AnalysisViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisViewModelTests.swift; sourceTree = "<group>"; };
126+
7B4C07BD2BB6C85D00B60CCD /* AnalysisViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisViewModel.swift; sourceTree = "<group>"; };
119127
7B571B532B95E5DA0019DEB8 /* MoodRecord+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoodRecord+CoreDataClass.swift"; sourceTree = "<group>"; };
120128
7B571B542B95E5DA0019DEB8 /* MoodRecord+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoodRecord+CoreDataProperties.swift"; sourceTree = "<group>"; };
121129
7B63CA9B2BA9CB4600943DED /* PlantingWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PlantingWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -272,6 +280,7 @@
272280
7B907F742B9997060050D05B /* DayCellViewModelTest.swift */,
273281
7B907F772B9B4CCE0050D05B /* FetchNotificationSpy.swift */,
274282
7B34415E2BA1F85600DA43F6 /* MonthPickerViewModelTests.swift */,
283+
7B4C07BB2BB6C1F200B60CCD /* AnalysisViewModelTests.swift */,
275284
);
276285
path = PlantingMindTests;
277286
sourceTree = "<group>";
@@ -303,6 +312,16 @@
303312
path = Picker;
304313
sourceTree = "<group>";
305314
};
315+
7B4C07B02BB6A4DD00B60CCD /* Analysis */ = {
316+
isa = PBXGroup;
317+
children = (
318+
7B4C07B52BB6B42E00B60CCD /* MoodAnalysisModel.swift */,
319+
7B4C07B72BB6B6B200B60CCD /* AnalysisView.swift */,
320+
7B4C07BD2BB6C85D00B60CCD /* AnalysisViewModel.swift */,
321+
);
322+
path = Analysis;
323+
sourceTree = "<group>";
324+
};
306325
7B63CA9C2BA9CB4600943DED /* Frameworks */ = {
307326
isa = PBXGroup;
308327
children = (
@@ -341,6 +360,7 @@
341360
7B2CFC1C2B5A6FFB00641F6D /* CalendarGridView.swift */,
342361
7B3441582BA1903300DA43F6 /* Day */,
343362
7B3441592BA190AE00DA43F6 /* Picker */,
363+
7B4C07B02BB6A4DD00B60CCD /* Analysis */,
344364
);
345365
path = Calender;
346366
sourceTree = "<group>";
@@ -520,8 +540,10 @@
520540
7B0A76D12B906FE100ADC039 /* CoreDataStack.swift in Sources */,
521541
7B907F7C2B9C6F1B0050D05B /* DateExtension.swift in Sources */,
522542
7B02AD622BA0222900D5C850 /* MonthPickerView.swift in Sources */,
543+
7B4C07B62BB6B42E00B60CCD /* MoodAnalysisModel.swift in Sources */,
523544
7BABB0112B5F850200EEEEBB /* ColorExtension.swift in Sources */,
524545
7B0A77112B93196D00ADC039 /* MoodRecordViewModel.swift in Sources */,
546+
7B4C07B82BB6B6B200B60CCD /* AnalysisView.swift in Sources */,
525547
7B0A76CE2B904A1200ADC039 /* MoodRecords.xcdatamodeld in Sources */,
526548
7BC300F52B5E4CE100059068 /* CalendarView.swift in Sources */,
527549
7B2CFC1D2B5A6FFB00641F6D /* CalendarGridView.swift in Sources */,
@@ -531,6 +553,7 @@
531553
7BAB04F42B9466BC00948CF2 /* Mood.swift in Sources */,
532554
7B2CFC1B2B5A6DE900641F6D /* DayCellView.swift in Sources */,
533555
7BDA3CD72B58F25F006D74FB /* CalendarHeaderView.swift in Sources */,
556+
7B4C07BE2BB6C85D00B60CCD /* AnalysisViewModel.swift in Sources */,
534557
7B907F732B9981E90050D05B /* DayCellViewModel.swift in Sources */,
535558
7B571B552B95E5DA0019DEB8 /* MoodRecord+CoreDataClass.swift in Sources */,
536559
7B11F8282B4549DF00346A41 /* PlantingMindApp.swift in Sources */,
@@ -543,6 +566,7 @@
543566
files = (
544567
7BAB04F62B946B8700948CF2 /* MoodRecordViewModelTests.swift in Sources */,
545568
7B34415F2BA1F85600DA43F6 /* MonthPickerViewModelTests.swift in Sources */,
569+
7B4C07BC2BB6C1F200B60CCD /* AnalysisViewModelTests.swift in Sources */,
546570
7B907F782B9B4CCE0050D05B /* FetchNotificationSpy.swift in Sources */,
547571
7B907F752B9997060050D05B /* DayCellViewModelTest.swift in Sources */,
548572
7BC300F92B5E5F8E00059068 /* CalendarViewModelTests.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// AnalysisView.swift
3+
// PlantingMind
4+
//
5+
// Created by 최은주 on 3/29/24.
6+
//
7+
8+
import SwiftUI
9+
import Charts
10+
11+
struct AnalysisView: View {
12+
var viewModel: AnalysisViewModel
13+
14+
var body: some View {
15+
if viewModel.recordsCount == 0 {
16+
Text("mood_statistics_empty")
17+
.multilineTextAlignment(.center)
18+
.font(.title3)
19+
.bold()
20+
.padding()
21+
} else {
22+
Text("mood_statistics")
23+
.font(.title3)
24+
.bold()
25+
.padding([.top, .horizontal])
26+
27+
Chart(viewModel.moodAnalysis, id: \.self) {
28+
BarMark(x: .value("Count", $0.count))
29+
.foregroundStyle(by: .value("Category", $0.mood))
30+
}
31+
.chartForegroundStyleScale([
32+
"nice": Mood.nice.color,
33+
"good": Mood.good.color,
34+
"normal": Mood.normal.color,
35+
"notBad": Mood.notBad.color,
36+
"bad": Mood.bad.color
37+
])
38+
.frame(height: 60)
39+
.padding()
40+
}
41+
}
42+
}
43+
44+
#Preview {
45+
AnalysisView(viewModel: AnalysisViewModel(moods: []))
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// AnalysisViewModel.swift
3+
// PlantingMind
4+
//
5+
// Created by 최은주 on 3/29/24.
6+
//
7+
8+
import Foundation
9+
10+
struct AnalysisViewModel {
11+
let moodAnalysis: [MoodAnalysis]
12+
13+
var recordsCount: Int {
14+
moodAnalysis.map { $0.count }.reduce(0,+)
15+
}
16+
17+
init(moods: [MoodRecord]) {
18+
var result: [MoodAnalysis] = []
19+
for mood in Mood.allCases {
20+
let count = moods.filter { $0.mood == mood.rawValue }.count
21+
result.append(MoodAnalysis(mood: mood.rawValue, count: count))
22+
}
23+
24+
self.moodAnalysis = result
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// MoodAnalysisModel.swift
3+
// PlantingMind
4+
//
5+
// Created by 최은주 on 3/29/24.
6+
//
7+
8+
import Foundation
9+
10+
struct MoodAnalysis: Hashable {
11+
let mood: String
12+
let count: Int
13+
}

PlantingMind/PlantingMind/Calender/CalendarView.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@ struct CalendarView: View {
1212
@StateObject var calendarViewModel: CalendarViewModel
1313

1414
var body: some View {
15-
VStack {
16-
CalendarHeaderView()
17-
CalendarGridView()
18-
Spacer()
15+
ScrollView {
16+
VStack() {
17+
CalendarHeaderView()
18+
CalendarGridView()
19+
Divider()
20+
.overlay(Color.Custom.line)
21+
22+
AnalysisView(viewModel: AnalysisViewModel(moods: calendarViewModel.moods))
23+
Spacer()
24+
}
1925
}
2026
.environmentObject(calendarViewModel)
2127
.onChange(of: phase) { newValue in

PlantingMind/PlantingMind/ContentView.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct ContentView: View {
1414
VStack {
1515
CalendarView(calendarViewModel: CalendarViewModel(today: Date(), context: context))
1616
}
17-
.padding()
17+
.padding([.horizontal, .top])
1818
}
1919
}
2020

PlantingMind/PlantingMind/Localization/Localizable.xcstrings

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
{
22
"sourceLanguage" : "en",
33
"strings" : {
4-
"" : {
5-
6-
},
7-
" - " : {
8-
9-
},
10-
"%@" : {
11-
12-
},
13-
"%lld" : {
14-
15-
},
164
"app_title" : {
175
"extractionState" : "manual",
186
"localizations" : {
@@ -148,6 +136,40 @@
148136
}
149137
}
150138
},
139+
"mood_statistics" : {
140+
"extractionState" : "manual",
141+
"localizations" : {
142+
"en" : {
143+
"stringUnit" : {
144+
"state" : "translated",
145+
"value" : "Mood Statistics"
146+
}
147+
},
148+
"ko" : {
149+
"stringUnit" : {
150+
"state" : "translated",
151+
"value" : "마음 현황"
152+
}
153+
}
154+
}
155+
},
156+
"mood_statistics_empty" : {
157+
"extractionState" : "manual",
158+
"localizations" : {
159+
"en" : {
160+
"stringUnit" : {
161+
"state" : "translated",
162+
"value" : "There are no mood records."
163+
}
164+
},
165+
"ko" : {
166+
"stringUnit" : {
167+
"state" : "translated",
168+
"value" : "이달에는 심어둔 마음이 없어요.\n마음 현황을 확인하려면\n기록을 추가해보세요."
169+
}
170+
}
171+
}
172+
},
151173
"mood_title" : {
152174
"extractionState" : "manual",
153175
"localizations" : {
@@ -303,4 +325,4 @@
303325
}
304326
},
305327
"version" : "1.0"
306-
}
328+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// AnalysisViewModelTests.swift
3+
// PlantingMindTests
4+
//
5+
// Created by 최은주 on 3/29/24.
6+
//
7+
8+
import XCTest
9+
@testable import PlantingMind
10+
11+
final class AnalysisViewModelTests: XCTestCase {
12+
var viewModel: AnalysisViewModel!
13+
14+
override func setUpWithError() throws {
15+
let context = CoreDataStack(.inMemory).persistentContainer.viewContext
16+
17+
let record1 = MoodRecord(context: context)
18+
record1.mood = Mood.nice.rawValue
19+
20+
let record2 = MoodRecord(context: context)
21+
record2.mood = Mood.bad.rawValue
22+
23+
var records: [MoodRecord] = [record1, record2]
24+
25+
viewModel = AnalysisViewModel(moods: records)
26+
}
27+
28+
func test_MoodRecord_to_MoodAnalysis_변환_체크() throws {
29+
let expectedCount = [1, 0, 0, 0, 1]
30+
let result = viewModel.moodAnalysis.map { $0.count }
31+
32+
XCTAssertEqual(expectedCount, result)
33+
}
34+
35+
func test_전체_기록_카운트() {
36+
XCTAssertEqual(viewModel.recordsCount, 2)
37+
}
38+
}

0 commit comments

Comments
 (0)