Skip to content

Commit a45061f

Browse files
authored
Merge pull request #103 from boostcampwm-2022/feature/CalendarView
Feature/calendar view
2 parents 14c4342 + 9e116c9 commit a45061f

File tree

12 files changed

+372
-157
lines changed

12 files changed

+372
-157
lines changed

DailyQuest/DailyQuest.xcodeproj/project.pbxproj

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,13 @@
197197
A5D3C850293DF70B00F43B76 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A5D3C84F293DF70B00F43B76 /* FirebaseFirestoreSwift */; };
198198
A5D3C852293DF70B00F43B76 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A5D3C851293DF70B00F43B76 /* FirebaseStorage */; };
199199
B50078D629222F3F0070AFC4 /* CircleCheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50078D529222F3F0070AFC4 /* CircleCheckView.swift */; };
200-
B5115453292CD07100FDBD22 /* CalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5115452292CD07100FDBD22 /* CalendarViewModel.swift */; };
200+
B559CE90293DED99001F0987 /* HomeCalendarUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5606666293DDA0900CEFEA8 /* HomeCalendarUseCase.swift */; };
201+
B5606665293DD9A000CEFEA8 /* CalendarUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5606664293DD9A000CEFEA8 /* CalendarUseCase.swift */; };
202+
B5606667293DDA0900CEFEA8 /* HomeCalendarUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5606666293DDA0900CEFEA8 /* HomeCalendarUseCase.swift */; };
201203
B5833F732924C08900503E0D /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5833F722924C08900503E0D /* CalendarView.swift */; };
202204
B58DFC0A29227DA800C68A4B /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58DFC0929227DA800C68A4B /* CalendarCell.swift */; };
205+
B599EAC2293F3D4D000EC069 /* DailyQuestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B599EAC1293F3D4D000EC069 /* DailyQuestCompletion.swift */; };
206+
B599EAC4293F3D6C000EC069 /* MonthlyQuestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B599EAC3293F3D6C000EC069 /* MonthlyQuestCompletion.swift */; };
203207
/* End PBXBuildFile section */
204208

205209
/* Begin PBXContainerItemProxy section */
@@ -365,9 +369,12 @@
365369
A5D3C822293DDDAD00F43B76 /* ProtectedUserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtectedUserRepository.swift; sourceTree = "<group>"; };
366370
A5D3C827293DDEBE00F43B76 /* QuestsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestsRepositoryTests.swift; sourceTree = "<group>"; };
367371
B50078D529222F3F0070AFC4 /* CircleCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleCheckView.swift; sourceTree = "<group>"; };
368-
B5115452292CD07100FDBD22 /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = "<group>"; };
372+
B5606664293DD9A000CEFEA8 /* CalendarUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarUseCase.swift; sourceTree = "<group>"; };
373+
B5606666293DDA0900CEFEA8 /* HomeCalendarUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCalendarUseCase.swift; sourceTree = "<group>"; };
369374
B5833F722924C08900503E0D /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
370375
B58DFC0929227DA800C68A4B /* CalendarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarCell.swift; sourceTree = "<group>"; };
376+
B599EAC1293F3D4D000EC069 /* DailyQuestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyQuestCompletion.swift; sourceTree = "<group>"; };
377+
B599EAC3293F3D6C000EC069 /* MonthlyQuestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyQuestCompletion.swift; sourceTree = "<group>"; };
371378
/* End PBXFileReference section */
372379

373380
/* Begin PBXFrameworksBuildPhase section */
@@ -457,6 +464,7 @@
457464
3416FC89292B560800B504C5 /* DefaultQuestUseCase.swift */,
458465
34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */,
459466
A5003ABA293F914500082A9C /* DefaultUserUseCase.swift */,
467+
B5606666293DDA0900CEFEA8 /* HomeCalendarUseCase.swift */,
460468
);
461469
path = Home;
462470
sourceTree = "<group>";
@@ -467,6 +475,7 @@
467475
3416FC87292B54DB00B504C5 /* QuestUseCase.swift */,
468476
344A4599293DC495007A3D37 /* EnrollUseCase.swift */,
469477
A5003AB8293F909300082A9C /* UserUseCase.swift */,
478+
B5606664293DD9A000CEFEA8 /* CalendarUseCase.swift */,
470479
);
471480
path = Protocols;
472481
sourceTree = "<group>";
@@ -609,6 +618,8 @@
609618
A51F01D9292345990031ECA2 /* BrowseQuest.swift */,
610619
3449AD5A2922164B00B87619 /* Quest.swift */,
611620
3449AD5C2922197000B87619 /* User.swift */,
621+
B599EAC1293F3D4D000EC069 /* DailyQuestCompletion.swift */,
622+
B599EAC3293F3D6C000EC069 /* MonthlyQuestCompletion.swift */,
612623
);
613624
path = Entities;
614625
sourceTree = "<group>";
@@ -782,7 +793,6 @@
782793
isa = PBXGroup;
783794
children = (
784795
345687FD29378AB900CA51E3 /* EnrollViewModel.swift */,
785-
B5115452292CD07100FDBD22 /* CalendarViewModel.swift */,
786796
9BED4DE7293FA01400C60631 /* ProfileViewModel.swift */,
787797
348FCE012940294900E4940C /* HomeViewModel.swift */,
788798
);
@@ -1267,6 +1277,7 @@
12671277
342830F6292E1ACA00AE811B /* PlainItemViewModel.swift in Sources */,
12681278
345687FE29378AB900CA51E3 /* EnrollViewModel.swift in Sources */,
12691279
34A529D129247880001BAD34 /* Coordinator.swift in Sources */,
1280+
B5606665293DD9A000CEFEA8 /* CalendarUseCase.swift in Sources */,
12701281
34113BEB2934A3B200AB4919 /* LoginViewController.swift in Sources */,
12711282
340A724929348B1B00B26AA6 /* AuthUseCase.swift in Sources */,
12721283
3499552029234637007AB99E /* CustomProgressBar.swift in Sources */,
@@ -1282,7 +1293,6 @@
12821293
A50F9A3F292679BC005C00FE /* NetworkConfigure.swift in Sources */,
12831294
34404D92293EF75F0007E661 /* SettingsViewModel.swift in Sources */,
12841295
34A529E7292481E1001BAD34 /* BrowseCoordinator.swift in Sources */,
1285-
B5115453292CD07100FDBD22 /* CalendarViewModel.swift in Sources */,
12861296
34A529D329247903001BAD34 /* TabCoordinator.swift in Sources */,
12871297
3417B1462935DA9D00900454 /* DefaultBrowseUseCase.swift in Sources */,
12881298
347D258D292C6E220038FCA2 /* MessageBubble.swift in Sources */,
@@ -1294,6 +1304,7 @@
12941304
3449AD5D2922197000B87619 /* User.swift in Sources */,
12951305
A5003AB6293F601E00082A9C /* SignUpViewController.swift in Sources */,
12961306
9BD8CCF52935C38300E6EA2F /* UserDTO+Mapping.swift in Sources */,
1307+
B599EAC2293F3D4D000EC069 /* DailyQuestCompletion.swift in Sources */,
12971308
A51F01CD29233ABB0031ECA2 /* RealmUserInfoStorage.swift in Sources */,
12981309
B5833F732924C08900503E0D /* CalendarView.swift in Sources */,
12991310
A50F9A3429266F45005C00FE /* NetworkService.swift in Sources */,
@@ -1328,6 +1339,7 @@
13281339
34113BE82934917500AB4919 /* LoginViewModel.swift in Sources */,
13291340
34EE6EB72924C674005AF583 /* QuestView.swift in Sources */,
13301341
B58DFC0A29227DA800C68A4B /* CalendarCell.swift in Sources */,
1342+
B5606667293DDA0900CEFEA8 /* HomeCalendarUseCase.swift in Sources */,
13311343
3499551529232533007AB99E /* UIColor+.swift in Sources */,
13321344
9BD8CD00293829DD00E6EA2F /* FollowingView.swift in Sources */,
13331345
342830F8292E1B7400AE811B /* PlainField.swift in Sources */,
@@ -1341,6 +1353,7 @@
13411353
A51189C329226E66008A9D33 /* QuestEntity+Mapping.swift in Sources */,
13421354
A51F01D3292340360031ECA2 /* BrowseQuestsStorage.swift in Sources */,
13431355
A50F9A3729266F6F005C00FE /* FirebaseService.swift in Sources */,
1356+
B599EAC4293F3D6C000EC069 /* MonthlyQuestCompletion.swift in Sources */,
13441357
9B1CFB3F292B585700CCE97A /* QuestDTO+Mapping.swift in Sources */,
13451358
34874AA0292509A4000570DF /* QuestViewHeader.swift in Sources */,
13461359
342830F4292E19B700AE811B /* PlainCell.swift in Sources */,
@@ -1412,6 +1425,7 @@
14121425
A5D3C840293DDFD400F43B76 /* SubQuestEntity.swift in Sources */,
14131426
34EE0C652935FD79002BEC23 /* BrowseViewModel.swift in Sources */,
14141427
A5D3C830293DDF9B00F43B76 /* DefaultQuestsRepository.swift in Sources */,
1428+
B559CE90293DED99001F0987 /* HomeCalendarUseCase.swift in Sources */,
14151429
340FDFDE292B7A2C00C4E3DC /* QuestUseCaseMock.swift in Sources */,
14161430
34EE0C542935F8F7002BEC23 /* BrowseQuest.swift in Sources */,
14171431
A5D3C83A293DDFBE00F43B76 /* RealmBrowseQuestsStorage.swift in Sources */,

DailyQuest/DailyQuest/Application/DIContainer/HomeSceneDIContainer.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,19 @@ final class HomeSceneDIContainer {
3030
func makeEnrollUseCase() -> EnrollUseCase {
3131
return DefaultEnrollUseCase(questsRepository: makeQuestsRepository())
3232
}
33+
34+
func makeCalendarUseCase() -> CalendarUseCase {
35+
return HomeCalendarUseCase(questsRepository: makeQuestsRepository())
36+
}
3337

3438
func makeUesrUseCase() -> UserUseCase {
3539
return DefaultUserUseCase(userRepository: makeUserRepository())
3640
}
37-
41+
3842
// MARK: - View Models
3943
func makeHomeViewModel() -> HomeViewModel {
40-
return HomeViewModel(questUseCase: makeQuestUseCase())
44+
return HomeViewModel(questUseCase: makeQuestUseCase(),
45+
calendarUseCase: makeCalendarUseCase())
4146
}
4247

4348
func makeEnrollViewModel() -> EnrollViewModel {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// DailyQuestCompletion.swift
3+
// DailyQuest
4+
//
5+
// Created by wickedRun on 2022/12/06.
6+
//
7+
8+
import Foundation
9+
10+
struct DailyQuestCompletion: Hashable {
11+
12+
enum State: Hashable {
13+
case hidden
14+
case normal
15+
case notDone(Int)
16+
case done
17+
}
18+
19+
let day: Date
20+
let state: State
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// MonthlyQuestCompletion.swift
3+
// DailyQuest
4+
//
5+
// Created by wickedRun on 2022/12/06.
6+
//
7+
8+
import Foundation
9+
10+
struct MonthlyQuestCompletion {
11+
12+
let month: Date
13+
let states: [DailyQuestCompletion]
14+
}
15+
16+
extension MonthlyQuestCompletion: Comparable {
17+
18+
static func < (lhs: MonthlyQuestCompletion, rhs: MonthlyQuestCompletion) -> Bool {
19+
return lhs.month < rhs.month
20+
}
21+
}
22+
23+
extension MonthlyQuestCompletion: Equatable {
24+
25+
static func == (lhs: MonthlyQuestCompletion, rhs: MonthlyQuestCompletion) -> Bool {
26+
return lhs.month == rhs.month
27+
}
28+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// HomeCalendarUseCase.swift
3+
// DailyQuest
4+
//
5+
// Created by wickedRun on 2022/12/05.
6+
//
7+
8+
import Foundation
9+
10+
import RxSwift
11+
12+
final class HomeCalendarUseCase: CalendarUseCase {
13+
private let questsRepository: QuestsRepository
14+
private let disposeBag = DisposeBag()
15+
16+
let currentMonth = BehaviorSubject<Date?>(value: Date())
17+
let completionOfMonths = BehaviorSubject<[[DailyQuestCompletion]]>(value: [[], [], []])
18+
19+
init(questsRepository: QuestsRepository) {
20+
self.questsRepository = questsRepository
21+
}
22+
23+
func fetchNextMontlyCompletion() {
24+
guard let nextMonth = try? currentMonth.value()?.startDayOfNextMonth else { return }
25+
currentMonth.onNext(nextMonth)
26+
27+
let monthAfterNext = nextMonth.startDayOfNextMonth
28+
29+
fetchAMontlyCompletion(monthAfterNext)
30+
.subscribe(onNext: { [weak self] monthlyCompletion in
31+
guard
32+
let self,
33+
var values = try? self.completionOfMonths.value()
34+
else {
35+
return
36+
}
37+
38+
values.removeFirst()
39+
values.append(monthlyCompletion)
40+
41+
self.completionOfMonths.onNext(values)
42+
})
43+
.disposed(by: disposeBag)
44+
}
45+
46+
func fetchLastMontlyCompletion() {
47+
guard let lastMonth = try? currentMonth.value()?.startDayOfLastMonth else { return }
48+
currentMonth.onNext(lastMonth)
49+
50+
let monthBeforeLast = lastMonth.startDayOfLastMonth
51+
52+
fetchAMontlyCompletion(monthBeforeLast)
53+
.subscribe(onNext: { [weak self] monthlyCompletion in
54+
guard
55+
let self,
56+
var values = try? self.completionOfMonths.value()
57+
else {
58+
return
59+
}
60+
61+
values.removeLast()
62+
values.insert(monthlyCompletion, at: 0)
63+
64+
self.completionOfMonths.onNext(values)
65+
})
66+
.disposed(by: disposeBag)
67+
}
68+
}
69+
70+
extension HomeCalendarUseCase {
71+
72+
private func calculateDailyState(_ quests: [Quest]) -> DailyQuestCompletion.State {
73+
guard !quests.isEmpty else {
74+
return .normal
75+
}
76+
77+
let result = quests.reduce(0) { partialResult, quest in
78+
partialResult + (quest.state ? 1 : 0)
79+
}
80+
81+
if result == quests.count {
82+
return .done
83+
} else {
84+
return .notDone(result)
85+
}
86+
}
87+
88+
func setupMonths() {
89+
let currentMonth = try? currentMonth.value()
90+
let startDayOfLastMonth = currentMonth?.startDayOfLastMonth
91+
let startDayOfCurrentMonth = currentMonth?.startDayOfCurrentMonth
92+
let startDayOfNextMonth = currentMonth?.startDayOfNextMonth
93+
94+
let months = [startDayOfLastMonth, startDayOfCurrentMonth, startDayOfNextMonth]
95+
96+
Observable.from(months)
97+
.concatMap { [weak self] monthDate in
98+
guard let self else { return Observable<[DailyQuestCompletion]>.empty() }
99+
100+
return self.fetchAMontlyCompletion(monthDate)
101+
}
102+
.toArray()
103+
.subscribe(onSuccess: { [weak self] completionOfMonths in
104+
self?.completionOfMonths.onNext(completionOfMonths)
105+
})
106+
.disposed(by: disposeBag)
107+
}
108+
109+
private func fetchAMontlyCompletion(_ month: Date?) -> Observable<[DailyQuestCompletion]> {
110+
guard let month = month else { return .empty() }
111+
112+
return Observable.from(month.rangeDaysOfMonth)
113+
.concatMap { [weak self] date -> Observable<DailyQuestCompletion> in
114+
guard let self else { return Observable.empty() }
115+
116+
return self.questsRepository
117+
.fetch(by: date)
118+
.map { quests -> DailyQuestCompletion in
119+
return DailyQuestCompletion(day: date, state: self.calculateDailyState(quests))
120+
}
121+
}
122+
.toArray()
123+
.map { states in
124+
let firstWeekStates = month.rangeFromStartWeekdayOfLastMonthToEndDayOfCurrentMonth
125+
.map { date -> DailyQuestCompletion in
126+
return DailyQuestCompletion(day: date, state: .hidden)
127+
}
128+
129+
return firstWeekStates + states
130+
}
131+
.asObservable()
132+
}
133+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// CalendarUseCase.swift
3+
// DailyQuest
4+
//
5+
// Created by wickedRun on 2022/12/05.
6+
//
7+
8+
import Foundation
9+
10+
import RxSwift
11+
12+
protocol CalendarUseCase {
13+
14+
var currentMonth: BehaviorSubject<Date?> { get }
15+
var completionOfMonths: BehaviorSubject<[[DailyQuestCompletion]]> { get }
16+
17+
func fetchNextMontlyCompletion()
18+
func fetchLastMontlyCompletion()
19+
func setupMonths()
20+
}

0 commit comments

Comments
 (0)