Skip to content

Commit

Permalink
Merge pull request #165 from DeveloperAcademy-POSTECH/hotfix/164-read…
Browse files Browse the repository at this point in the history
…ing-schedule-crash

[#164] 독서 기간 초과 알림 및 연장 기능 추가
  • Loading branch information
zaehorang authored Jan 14, 2025
2 parents 80986b3 + 583014e commit 44d4225
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 18 deletions.
12 changes: 12 additions & 0 deletions FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
24FAF9452CEC5224004FEC3F /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FAF9442CEC5224004FEC3F /* ToastView.swift */; };
2613B7522D329FD900AC717F /* WeeklyProgressPagingSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2613B7512D329FD900AC717F /* WeeklyProgressPagingSlider.swift */; };
2613B7582D34F81E00AC717F /* EmptyImageDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2613B7572D34F81E00AC717F /* EmptyImageDefaultView.swift */; };
2613B7652D3559D400AC717F /* UnfinishReadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2613B7642D3559D400AC717F /* UnfinishReadingView.swift */; };
261714D62CDDDA4000A3241D /* BookSettingsManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261714D52CDDDA4000A3241D /* BookSettingsManagerView.swift */; };
2622C7D82D2AC4C000AA3FF4 /* DividerLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2622C7D72D2AC4C000AA3FF4 /* DividerLine.swift */; };
2622C7DA2D2AC56400AA3FF4 /* CalendarWeekdayHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2622C7D92D2AC56400AA3FF4 /* CalendarWeekdayHeader.swift */; };
Expand Down Expand Up @@ -130,6 +131,7 @@
24FAF9442CEC5224004FEC3F /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
2613B7512D329FD900AC717F /* WeeklyProgressPagingSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeeklyProgressPagingSlider.swift; sourceTree = "<group>"; };
2613B7572D34F81E00AC717F /* EmptyImageDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyImageDefaultView.swift; sourceTree = "<group>"; };
2613B7642D3559D400AC717F /* UnfinishReadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinishReadingView.swift; sourceTree = "<group>"; };
261714D52CDDDA4000A3241D /* BookSettingsManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookSettingsManagerView.swift; sourceTree = "<group>"; };
2622C7D72D2AC4C000AA3FF4 /* DividerLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DividerLine.swift; sourceTree = "<group>"; };
2622C7D92D2AC56400AA3FF4 /* CalendarWeekdayHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarWeekdayHeader.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -264,6 +266,14 @@
path = Model;
sourceTree = "<group>";
};
2613B7612D35592400AC717F /* New Group */ = {
isa = PBXGroup;
children = (
2613B7642D3559D400AC717F /* UnfinishReadingView.swift */,
);
path = "New Group";
sourceTree = "<group>";
};
2622C8032D2D911200AA3FF4 /* Repository */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -425,6 +435,7 @@
26D852FB2CCE40BC0016239A /* Screen */ = {
isa = PBXGroup;
children = (
2613B7612D35592400AC717F /* New Group */,
26F19A662CD9EF9400F41D6D /* Main */,
26410A962D26D0FF00D6B477 /* ReadingCalendar */,
1A27D95E2CDA155B00D1E14D /* BookSetting */,
Expand Down Expand Up @@ -804,6 +815,7 @@
264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */,
26EBDEBE2CF2390C00B3A2BC /* CompletionStatusProtocol.swift in Sources */,
264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */,
2613B7652D3559D400AC717F /* UnfinishReadingView.swift in Sources */,
2655532F2CF6D73900288037 /* NotiSettingView.swift in Sources */,
26890B952CAE811A008DFF49 /* FiveGuyesApp.swift in Sources */,
26CF82582D1D3216005037E4 /* ReadingDateCalculator.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions FiveGuyes/FiveGuyes/Resources/Extensions/View+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ extension View {
RoundedRectangle(cornerSize: CGSize(width: bottomTrailingRadius, height: topTrailingRadius))
)
}

/// 네비게이션 드래그 제스처를 비활성화합니다.
func disableNavigationGesture() -> some View {
self.gesture(DragGesture().onChanged { _ in })
}
}
25 changes: 18 additions & 7 deletions FiveGuyes/FiveGuyes/Sources/Models/ReadingScheduleCalculator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

import Foundation

enum ReadingScheduleError: Error {
case targetDatePassed
}

/// 독서 스케줄을 계산하고 관리하는 구조체
struct ReadingScheduleCalculator {
private let readingPagesCalculator: ReadingPagesCalculator = ReadingPagesCalculator()
Expand Down Expand Up @@ -129,15 +133,20 @@ struct ReadingScheduleCalculator {
func reassignPagesFromLastReadDate<Settings: UserSettingsProtocol, Progress: ReadingProgressProtocol>(
settings: Settings,
progress: Progress
) {
) throws {
let adjustedToday = Date().adjustedDate()

// 완독 날짜가 지나면 에러 처리
if adjustedToday.onlyDate > settings.targetEndDate.onlyDate {
throw ReadingScheduleError.targetDatePassed
}

// 이미 오늘 읽은 페이지가 기록되었으면 재분배를 수행하지 않음
if hasReadPagesAdjustedToday(progress: progress) {
print("페이지 재분배1 ❌❌❌ ")
return
}

let adjustedToday = Date().adjustedDate()

// TODO: 독서 시작 날짜와 조정된 오늘 날짜가 같은 날에는 재할당 막기
// 불필요한 계산
if settings.startDate.toYearMonthDayString() == adjustedToday.toYearMonthDayString() {
Expand Down Expand Up @@ -298,10 +307,13 @@ struct ReadingScheduleCalculator {

/// 오늘 할당량이 읽혔는지 확인하는 메서드
private func hasReadPagesAdjustedToday<Progress: ReadingProgressProtocol>(progress: Progress) -> Bool {
let adjustedToday = Date().adjustedDate()
let adjustedTodayKey = progress.getAdjustedReadingRecordsKey(adjustedToday)
let today = Date()
let adjustedTodayKey = progress.getAdjustedReadingRecordsKey(today)

// 해당 날짜에 기록이 없는 경우
guard let record = progress.readingRecords[adjustedTodayKey] else { return false }

return progress.readingRecords[adjustedTodayKey]?.pagesRead != 0
return record.pagesRead != 0
}
}

Expand Down Expand Up @@ -400,7 +412,6 @@ extension ReadingScheduleCalculator {
endDate: settings.targetEndDate
)
progress.readingRecords = filteredRecords

// 제외된 날짜 데이터를 필터링
let filteredExcludedDates = filteredProgressForExcludedDates(
progress: progress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum Screens: Hashable {
case completionReview(book: UserBook)
case completionReviewUpdate(book: UserBook)
case readingDateEdit(book: UserBook)
case unfinishReading(book: UserBook)
}

@Observable
Expand Down Expand Up @@ -50,6 +51,8 @@ final class NavigationCoordinator {
CompletionReviewView(isUpdateMode: true, userBook: book)
case .readingDateEdit(book: let book):
ReadingDateEditView(userBook: book)
case .unfinishReading(book: let book):
UnfinishReadingView(userBook: book)
}
}

Expand Down
22 changes: 15 additions & 7 deletions FiveGuyes/FiveGuyes/Sources/Views/Screen/Main/MainHomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ struct MainHomeView: View {
filter: #Predicate<UserBook> { $0.completionStatus.isCompleted == false },
sort: \UserBook.userSettings.targetEndDate) // 독서 종료 날짜를 기준으로 오름차순
private var currentlyReadingBooks: [UserBook]

@State private var activeBookID: UUID?

@State private var activeBookID: UUID?
@State private var selectedBookIndex: Int? = 0

private var selectedBook: UserBook? {
Expand Down Expand Up @@ -80,7 +79,7 @@ struct MainHomeView: View {
primaryButton: .cancel(Text("취소하기")),
secondaryButton: .destructive(Text("삭제")) {
if let selectedBookIndex {
deleteBook(at: selectedBookIndex)
deleteBook(at: selectedBookIndex)
}
}
)
Expand Down Expand Up @@ -313,10 +312,19 @@ struct MainHomeView: View {
let readingScheduleCalculator = ReadingScheduleCalculator()

for book in currentlyReadingBooks {
readingScheduleCalculator.reassignPagesFromLastReadDate(
settings: book.userSettings,
progress: book.readingProgress
)
do {
try readingScheduleCalculator
.reassignPagesFromLastReadDate(
settings: book.userSettings,
progress: book.readingProgress
)
} catch ReadingScheduleError.targetDatePassed {
// 종료 날짜 초과 에러가 발생한 경우 날짜 연장 뷰로 이동
navigationCoordinator.push(.unfinishReading(book: book))
continue
} catch {
print("예상치 못한 에러 발생: \(error.localizedDescription)")
}
}

// 데이터 저장
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//
// UnfinishGoalView.swift
// FiveGuyes
//
// Created by zaehorang on 1/13/25.
//

import SwiftUI

struct UnfinishReadingView: View {
typealias UserBook = UserBookSchemaV2.UserBookV2

@Environment(NavigationCoordinator.self) var navigationCoordinator: NavigationCoordinator

@Environment(\.modelContext) private var modelContext

private var userBook: UserBook

// MARK: - init

init(userBook: UserBook) {
self.userBook = userBook
}

var body: some View {
VStack(spacing: 0) {
Spacer()

unfinishTitle("목표 기간 종료!")
.padding(.bottom, 16)

unFinishDescription("\(userBook.readingProgress.lastPagesRead) 쪽까지 읽었어요!\n지속적인 노력이 중요하죠")
.padding(.bottom, 90)

userBookImage(book: userBook)
.commonShadow()
.padding(.bottom, 28)

unfinishMessage("목표 기간을 연장해서\n남은 페이지를 완독할 수 있어요")
.padding(.bottom, 80)

HStack(spacing: 16) {
cancelButton {
markBookAsCompleted()
navigationCoordinator.popToRoot()
}

readingDateEtidButton {
navigationCoordinator.push(.readingDateEdit(book: userBook))
}
}
.padding(.horizontal, 20)

Spacer()

}
.background(Color.Fills.lightGreen.ignoresSafeArea())
.disableNavigationGesture()
.customNavigationBackButton {
markBookAsCompleted()
}
}

private func unfinishTitle(_ text: String) -> some View {
Text(text)
.fontStyle(.body, weight: .semibold)
.foregroundStyle(.green)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background {
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color.Fills.white)
}
}

private func unFinishDescription(_ text: String) -> some View {
Text(text)
.fontStyle(.title1, weight: .semibold)
.foregroundStyle(Color.Labels.primaryBlack1)
.multilineTextAlignment(.center)
}

private func userBookImage(book: UserBook) -> some View {
Group {
if let urlString = book.bookMetaData.coverURL {
AsyncImage(url: URL(string: urlString)) { image in
image
.resizable()
} placeholder: {
ProgressView()
}
} else {
Image("")
.resizable()
.frame(width: 173)
}
}
.scaledToFit()
.frame(height: 267)
.clipToBookShape()
}

private func unfinishMessage(_ text: String) -> some View {
Text(text)
.fontStyle(.caption1)
.foregroundStyle(Color.Labels.primaryBlack1)
.multilineTextAlignment(.center)
.padding(.vertical, 4)
.padding(.horizontal, 10)
.background {
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color.Fills.white)
}
}

private func readingDateEtidButton(actions: @escaping () -> ()) -> some View {
Button(action: actions) {
Text("목표 기간 연장하기")
.fontStyle(.title2, weight: .semibold)
.foregroundStyle(Color.Fills.white)
.frame(maxWidth: .infinity)
.frame(height: 56)
.background {
RoundedRectangle(cornerRadius: 16)
.foregroundStyle(Color.Colors.green1)
}
}
}

private func cancelButton(actions: @escaping () -> ()) -> some View {
Button(action: actions) {
Text("닫기")
.fontStyle(.title2, weight: .semibold)
.foregroundStyle(Color.Labels.primaryBlack1)
.frame(width: 107, height: 56)
.background {
RoundedRectangle(cornerRadius: 16)
.foregroundStyle(Color.Fills.white)
}
}
}

// MARK: - Helper Methods

private func markBookAsCompleted() {
userBook.completionStatus.markAsCompleted(review: "")
do {
try modelContext.save()
} catch {
print("Error saving context: \(error)")
}
}
}

#Preview {
UnfinishReadingView(userBook: UserBookSchemaV2.UserBookV2.dummyUserBookV2)
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ struct ReadingDateEditView: View {
// 페이지 재할당 로직 호출
reassignPages()
// 페이지 나가기
navigationCoordinator.pop()
navigationCoordinator.popToRoot()
}
} label: {
RoundedRectangle(cornerRadius: 16)
Expand Down
10 changes: 7 additions & 3 deletions FiveGuyes/FiveGuyes/Sources/Views/Shared/CustomBackButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import SwiftUI

struct CustomBackButton: View {
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var action: (() -> Void)? // 추가 액션을 위한 옵셔널 클로저

var body: some View {
HStack {
Button {
action?() // 액션이 있으면 실행
presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Expand All @@ -29,16 +31,18 @@ struct CustomBackButton: View {
}

struct NavigationBackButtonModifier: ViewModifier {
var action: (() -> Void)? // 추가 액션

func body(content: Content) -> some View {
content
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: CustomBackButton())
.navigationBarItems(leading: CustomBackButton(action: action))
}
}

extension View {
func customNavigationBackButton() -> some View {
self.modifier(NavigationBackButtonModifier())
func customNavigationBackButton(action: (() -> Void)? = nil) -> some View {
self.modifier(NavigationBackButtonModifier(action: action))
}
}

Expand Down

0 comments on commit 44d4225

Please sign in to comment.