Skip to content

Commit 3679d09

Browse files
shp7724peng-u-0807
authored andcommitted
♻️ Toast 에러 처리 개선
1 parent 9f77297 commit 3679d09

27 files changed

+390
-208
lines changed

SNUTT/CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ tuist build "SNUTT Dev" # Build development configuration (RECOMMENDED)
2929
tuist build "SNUTT Prod" # Build production configuration
3030
tuist build "SNUTT Widget" # Build app + widget extension
3131

32+
# ⚠️ CRITICAL: ALWAYS run `tuist build` synchronously (NOT as a background job)!
33+
# Never use run_in_background=true or append '&' to build commands.
34+
# Wait for the build to complete before proceeding with other tasks.
35+
# This ensures proper error detection and prevents build state corruption.
36+
3237
# Module-Specific Builds (Preview Schemes)
3338
# Use these for faster builds when working on a single module:
3439
tuist build "Timetable Preview" # Build only Timetable module

SNUTT/Modules/Feature/APIClientInterface/Resources/Base.lproj/Localizable.strings

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,6 @@
129129
"error.failureReason.duplicateThemeName" = "테마 이름이 중복됩니다.";
130130
"error.recoverySuggestion.duplicateThemeName" = "다른 테마 이름을 사용해주세요.";
131131

132-
"error.description.deeplinkNotFound" = "딥링크 오류";
133-
"error.failureReason.deeplinkNotFound" = "딥링크 정보를 찾을 수 없습니다.";
134-
"error.recoverySuggestion.deeplinkNotFound" = "딥링크 정보를 확인하고 다시 시도해주세요.";
135-
136-
"error.description.deeplinkProcessFailed" = "딥링크 처리 오류";
137-
"error.failureReason.deeplinkProcessFailed" = "딥링크 처리에 실패했습니다.";
138-
"error.recoverySuggestion.deeplinkProcessFailed" = "딥링크 정보를 확인하고 다시 시도해주세요.";
139-
140132
"error.description.alreadyVerified" = "인증 오류";
141133
"error.failureReason.alreadyVerified" = "이미 인증된 계정입니다.";
142134

SNUTT/Modules/Feature/APIClientInterface/Resources/en.lproj/Localizable.strings

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,6 @@
129129
"error.failureReason.duplicateThemeName" = "The theme name is duplicated.";
130130
"error.recoverySuggestion.duplicateThemeName" = "Please use a different theme name.";
131131

132-
"error.description.deeplinkNotFound" = "Deeplink Error";
133-
"error.failureReason.deeplinkNotFound" = "The deeplink information cannot be found.";
134-
"error.recoverySuggestion.deeplinkNotFound" = "Please check the deeplink information and try again.";
135-
136-
"error.description.deeplinkProcessFailed" = "Deeplink Processing Error";
137-
"error.failureReason.deeplinkProcessFailed" = "Failed to process the deeplink.";
138-
"error.recoverySuggestion.deeplinkProcessFailed" = "Please check the deeplink information and try again.";
139-
140132
"error.description.alreadyVerified" = "Verification Error";
141133
"error.failureReason.alreadyVerified" = "The account is already verified.";
142134

SNUTT/Modules/Feature/APIClientInterface/Sources/Error/APIClientError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ extension ClientError: APIClientError {
2525
underlyingError as? ClientUnknownServerError
2626
}
2727
}
28+
29+
extension APIClientError {
30+
public var failureReason: String? {
31+
serverError?.failureReason ?? localizedCode?.failureReason
32+
}
33+
}

SNUTT/Modules/Feature/APIClientInterface/Sources/Error/LocalizedErrorCode.swift

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ public enum LocalizedErrorCode: Int, LocalizedError {
7272
case invalidLectureTime = 0x5002
7373
case invalidRnBundle = 0x5003
7474
case duplicateThemeName = 40904
75-
case deeplinkLectureNotFound = 0x5004
76-
case deeplinkTimetableNotFound = 0x5005
77-
case deeplinkBookmarkNotFound = 0x5006
78-
case deeplinkProcessFailed = 0x5007
7975

8076
case alreadyVerifiedAccount = 0x9000
8177
case alreadyVerifiedEmail = 0x9001
@@ -154,10 +150,6 @@ public enum LocalizedErrorCode: Int, LocalizedError {
154150
APIClientInterfaceStrings.errorDescriptionInvalidRnBundle
155151
case .duplicateThemeName:
156152
APIClientInterfaceStrings.errorDescriptionDuplicateThemeName
157-
case .deeplinkLectureNotFound, .deeplinkTimetableNotFound, .deeplinkBookmarkNotFound:
158-
APIClientInterfaceStrings.errorDescriptionDeeplinkNotFound
159-
case .deeplinkProcessFailed:
160-
APIClientInterfaceStrings.errorDescriptionDeeplinkProcessFailed
161153
case .alreadyVerifiedAccount, .alreadyVerifiedEmail:
162154
APIClientInterfaceStrings.errorDescriptionAlreadyVerified
163155
case .duplicateVacancyNotification:
@@ -241,10 +233,6 @@ public enum LocalizedErrorCode: Int, LocalizedError {
241233
APIClientInterfaceStrings.errorFailureReasonInvalidRnBundle
242234
case .duplicateThemeName:
243235
APIClientInterfaceStrings.errorFailureReasonDuplicateThemeName
244-
case .deeplinkLectureNotFound, .deeplinkTimetableNotFound, .deeplinkBookmarkNotFound:
245-
APIClientInterfaceStrings.errorFailureReasonDeeplinkNotFound
246-
case .deeplinkProcessFailed:
247-
APIClientInterfaceStrings.errorFailureReasonDeeplinkProcessFailed
248236
case .alreadyVerifiedAccount, .alreadyVerifiedEmail:
249237
APIClientInterfaceStrings.errorFailureReasonAlreadyVerified
250238
case .duplicateVacancyNotification:
@@ -328,10 +316,6 @@ public enum LocalizedErrorCode: Int, LocalizedError {
328316
APIClientInterfaceStrings.errorRecoverySuggestionInvalidRnBundle
329317
case .duplicateThemeName:
330318
APIClientInterfaceStrings.errorRecoverySuggestionDuplicateThemeName
331-
case .deeplinkLectureNotFound, .deeplinkTimetableNotFound, .deeplinkBookmarkNotFound:
332-
APIClientInterfaceStrings.errorRecoverySuggestionDeeplinkNotFound
333-
case .deeplinkProcessFailed:
334-
APIClientInterfaceStrings.errorRecoverySuggestionDeeplinkProcessFailed
335319
case .alreadyVerifiedAccount, .alreadyVerifiedEmail:
336320
nil
337321
case .duplicateVacancyNotification:

SNUTT/Modules/Feature/Friends/Sources/UI/FriendsScene.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,21 @@ import ThemesInterface
1313
import TimetableInterface
1414

1515
public struct FriendsScene: View {
16+
struct SelectedLecture: Identifiable {
17+
let id: String
18+
let lecture: Lecture
19+
let quarter: Quarter
20+
21+
init(lecture: Lecture, quarter: Quarter) {
22+
self.id = lecture.id
23+
self.lecture = lecture
24+
self.quarter = quarter
25+
}
26+
}
27+
1628
@AppStorage("isNewToFriendsService") private var isNewToFriendsService: Bool = true
1729
@State var viewModel: FriendsViewModel = .init()
18-
@State private var selectedLecture: Lecture?
30+
@State private var selectedLecture: SelectedLecture?
1931
@Environment(\.timetableUIProvider) private var timetableUIProvider
2032
@Environment(\.themeViewModel) private var themeViewModel
2133
@Environment(\.errorAlertHandler) private var errorAlertHandler
@@ -72,8 +84,12 @@ public struct FriendsScene: View {
7284
.sheet(isPresented: $viewModel.isRequestSheetPresented) {
7385
FriendRequestOptionSheet(friendsViewModel: viewModel)
7486
}
75-
.sheet(item: $selectedLecture) { lecture in
76-
timetableUIProvider.makeLectureDetailPreview(lecture: lecture, options: [.showDismissButton])
87+
.sheet(item: $selectedLecture) { selected in
88+
timetableUIProvider.makeLectureDetailPreview(
89+
lecture: selected.lecture,
90+
quarter: selected.quarter,
91+
options: [.showDismissButton]
92+
)
7793
}
7894
}
7995
}
@@ -135,7 +151,8 @@ public struct FriendsScene: View {
135151
.environment(
136152
\.lectureTapAction,
137153
LectureTapAction(action: { lecture in
138-
selectedLecture = lecture
154+
guard let quarter = friendContent?.selectedQuarter else { return }
155+
selectedLecture = SelectedLecture(lecture: lecture, quarter: quarter)
139156
})
140157
)
141158
.id(friendContent?.friend.id)

SNUTT/Modules/Feature/Timetable/Resources/Base.lproj/Localizable.strings

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,8 @@
133133
"toast.action.view" = "보기";
134134

135135
"edit.goToTimetable" = "해당 시간표로 이동";
136+
137+
"navigation.error.lecture_not_found" = "시간표에서 삭제된 강의입니다.";
138+
"navigation.error.timetable_not_found" = "존재하지 않는 시간표입니다.";
139+
"navigation.error.bookmark_not_found" = "관심강좌에서 삭제된 강의입니다.";
140+
"navigation.error.unknown" = "올바르지 않은 정보입니다.";

SNUTT/Modules/Feature/Timetable/Resources/en.lproj/Localizable.strings

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,8 @@
132132
"toast.action.view" = "View";
133133

134134
"edit.goToTimetable" = "Go to Timetable";
135+
136+
"navigation.error.lecture_not_found" = "Lecture has been deleted from timetable.";
137+
"navigation.error.timetable_not_found" = "Timetable does not exist.";
138+
"navigation.error.bookmark_not_found" = "Lecture has been deleted from bookmarks.";
139+
"navigation.error.unknown" = "Invalid information.";

SNUTT/Modules/Feature/Timetable/Sources/Presentation/LectureEditDetailViewModel.swift

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ public final class LectureEditDetailViewModel {
3232
@ObservationIgnored
3333
@Dependency(\.analyticsLogger) private var analyticsLogger
3434

35+
let displayMode: DisplayMode
3536
private let parentTimetable: Timetable?
37+
private let quarter: Quarter
3638

3739
var entryLecture: Lecture
3840
var editableLecture: Lecture
@@ -57,8 +59,19 @@ public final class LectureEditDetailViewModel {
5759
buildings.allSatisfy { $0.campus == .GWANAK }
5860
}
5961

60-
init(parentTimetable: Timetable?, entryLecture: Lecture) {
61-
self.parentTimetable = parentTimetable
62+
init(displayMode: DisplayMode, entryLecture: Lecture) {
63+
self.displayMode = displayMode
64+
65+
// Extract timetable and quarter from displayMode
66+
switch displayMode {
67+
case .normal(let timetable), .create(let timetable):
68+
self.parentTimetable = timetable
69+
self.quarter = timetable.quarter
70+
case .preview(_, let quarter):
71+
self.parentTimetable = nil
72+
self.quarter = quarter
73+
}
74+
6275
self.entryLecture = entryLecture
6376
editableLecture = entryLecture
6477
lectureID = entryLecture.id
@@ -206,12 +219,10 @@ public final class LectureEditDetailViewModel {
206219
}
207220

208221
func fetchSyllabusURL() async -> URL? {
209-
guard let parentTimetable,
210-
!entryLecture.isCustom
211-
else { return nil }
222+
guard !entryLecture.isCustom else { return nil }
212223

213-
let year = parentTimetable.quarter.year
214-
let semester = parentTimetable.quarter.semester.rawValue
224+
let year = quarter.year
225+
let semester = quarter.semester.rawValue
215226

216227
do {
217228
let syllabus = try await courseBookRepository.fetchSyllabusURL(
@@ -226,6 +237,63 @@ public final class LectureEditDetailViewModel {
226237
}
227238
}
228239

240+
extension LectureEditDetailViewModel {
241+
public enum DisplayMode {
242+
/// 내가 추가한 강의 상세
243+
case normal(timetable: Timetable)
244+
/// 새로운 강의 추가
245+
case create(timetable: Timetable)
246+
/// 내가 추가하지 않은 강의 상세 (검색 결과, 북마크 등)
247+
case preview(LectureDetailPreviewOptions, quarter: Quarter)
248+
249+
public var isPreview: Bool {
250+
if case .preview = self {
251+
return true
252+
}
253+
return false
254+
}
255+
256+
public var isCreate: Bool {
257+
if case .create = self {
258+
return true
259+
}
260+
return false
261+
}
262+
263+
public var isNormal: Bool {
264+
if case .normal = self {
265+
return true
266+
}
267+
return false
268+
}
269+
270+
public var previewOptions: LectureDetailPreviewOptions? {
271+
if case let .preview(options, _) = self {
272+
return options
273+
}
274+
return nil
275+
}
276+
277+
public var timetable: Timetable? {
278+
switch self {
279+
case .normal(let timetable), .create(let timetable):
280+
return timetable
281+
case .preview:
282+
return nil
283+
}
284+
}
285+
286+
public var quarter: Quarter {
287+
switch self {
288+
case .normal(let timetable), .create(let timetable):
289+
return timetable.quarter
290+
case .preview(_, let quarter):
291+
return quarter
292+
}
293+
}
294+
}
295+
}
296+
229297
extension Lecture {
230298
var quotaDescription: String? {
231299
get {

SNUTT/Modules/Feature/Timetable/Sources/Presentation/LectureListViewModel.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,9 @@ extension LectureListViewModel {
7070
}
7171

7272
func selectLecture(_ lecture: Lecture) {
73+
guard let currentTimetable = timetableViewModel.currentTimetable else { return }
7374
timetableViewModel.paths.append(
74-
.lectureDetail(
75-
lecture,
76-
parentTimetable: timetableViewModel.currentTimetable,
77-
belongsToOtherTimetable: false
78-
)
75+
.lectureDetail(lecture, parentTimetable: currentTimetable)
7976
)
8077
analyticsLogger.logScreen(
8178
AnalyticsScreen.lectureDetail(.init(lectureID: lecture.referenceID, referrer: .lectureList))

0 commit comments

Comments
 (0)