From 0d624f83d03afe34966c16a93bf99cce0902d98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9B=B0=EB=94=94?= Date: Fri, 8 Nov 2024 15:34:09 +0900 Subject: [PATCH 1/4] feat:#54-testConfirm feat:#54-cleaningCode_seperatedButton feat:#54-add_description feat:#54-updateForGitPush --- FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj | 7 + .../xcshareddata/swiftpm/Package.resolved | 14 - .../Stores/PushNotificationTestView.swift | 264 ++++++++++++++++++ 3 files changed, 271 insertions(+), 14 deletions(-) delete mode 100644 FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift diff --git a/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj b/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj index 0424302..81db57e 100644 --- a/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj +++ b/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 1A27D9662CDB779000D1E14D /* TotalCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A27D9652CDB779000D1E14D /* TotalCalendarView.swift */; }; 1A54142B2CD9FEF400283FBD /* BookSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A54142A2CD9FEF400283FBD /* BookSearchView.swift */; }; 1AA14A782CDDA30D00B763A6 /* TotalCalendarTextBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA14A772CDDA30D00B763A6 /* TotalCalendarTextBubble.swift */; }; + 1AA14A7A2CDDD96200B763A6 /* PushNotificationTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */; }; + 24360D462CD8A2F800E83D2B /* EmptyDataMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D452CD8A2F800E83D2B /* EmptyDataMainView.swift */; }; 24360D482CD8BAF100E83D2B /* EmptyNotiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D472CD8BAF100E83D2B /* EmptyNotiView.swift */; }; 24360D4A2CD8BE3C00E83D2B /* FinishGoalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D492CD8BE3C00E83D2B /* FinishGoalView.swift */; }; 24360D532CD9F3AF00E83D2B /* DailyProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D522CD9F3AF00E83D2B /* DailyProgressView.swift */; }; @@ -63,6 +65,8 @@ 1A27D9652CDB779000D1E14D /* TotalCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCalendarView.swift; sourceTree = ""; }; 1A54142A2CD9FEF400283FBD /* BookSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BookSearchView.swift; path = FiveGuyes/Sources/Views/Screen/BookSetting/BookSearch/BookSearchView.swift; sourceTree = SOURCE_ROOT; }; 1AA14A772CDDA30D00B763A6 /* TotalCalendarTextBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCalendarTextBubble.swift; sourceTree = ""; }; + 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationTestView.swift; sourceTree = ""; }; + 24360D452CD8A2F800E83D2B /* EmptyDataMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyDataMainView.swift; sourceTree = ""; }; 24360D472CD8BAF100E83D2B /* EmptyNotiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyNotiView.swift; sourceTree = ""; }; 24360D492CD8BE3C00E83D2B /* FinishGoalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinishGoalView.swift; sourceTree = ""; }; 24360D522CD9F3AF00E83D2B /* DailyProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyProgressView.swift; sourceTree = ""; }; @@ -253,6 +257,7 @@ 264440542CD8E0100031A290 /* KeyboardObserver.swift */, 26F19A712CDB267600F41D6D /* NavigationCoordinator.swift */, 26EBDE252CDE3C6000B3A2BC /* BookSettingInputModel.swift */, + 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */, ); path = Stores; sourceTree = ""; @@ -416,6 +421,8 @@ 24360D4A2CD8BE3C00E83D2B /* FinishGoalView.swift in Sources */, 26F19A682CD9EFD800F41D6D /* MainHomeView.swift in Sources */, 26F19A742CDB271E00F41D6D /* NavigationRootView.swift in Sources */, + 24360D462CD8A2F800E83D2B /* EmptyDataMainView.swift in Sources */, + 1AA14A7A2CDDD96200B763A6 /* PushNotificationTestView.swift in Sources */, 264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */, 264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */, 26890B952CAE811A008DFF49 /* FiveGuyesApp.swift in Sources */, diff --git a/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 17b1024..0000000 --- a/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftlintplugins", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", - "state" : { - "revision" : "7c80ce6f142164b0201871e580b021d1b2c69804", - "version" : "0.57.0" - } - } - ], - "version" : 2 -} diff --git a/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift b/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift new file mode 100644 index 0000000..8351e21 --- /dev/null +++ b/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift @@ -0,0 +1,264 @@ +// +// PushNotificationTest.swift +// FiveGuyes +// +// Created by Shim Hyeonhee on 11/8/24. +// + +import SwiftUI +import UserNotifications + + + +// 노티피케이션 테스트용 매니저 +class NotificationManager { + // 싱글톤 설정 + + static let shared = NotificationManager() + + /// 초기 알람 설정을 위한 권한 호출 + func requestNotificationPermission() { + let options: UNAuthorizationOptions = [.alert, .sound, .badge] + UNUserNotificationCenter.current().requestAuthorization(options: options) { suceess, error in + if let error { + print("알람 권한 호출에 문제가 있어요 에러: \(error)") + } else { + print("성공적으료 알람을 위한 권한을 호출했어요") + } + } + } + + // 특정 시간 알람 + func scheduleSpecificTimeNotification(hour: Int, minute: Int) { + let content = UNMutableNotificationContent() + content.title = "특정시간알람" + content.body = "오늘도 꼭 완독하세요!" + content.sound = .default + + var dateComponents = DateComponents() + dateComponents.hour = hour + dateComponents.minute = minute + + let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) + let request = UNNotificationRequest(identifier: "specificTimeNoti", content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("특정 시간 알람이 설정 되지 않았어요 \(error)") + } else { + print("특정 시간 알람을 설정했어요") + } + } + } + + + func scheduleReminderNotification(readingData: [PushNotificationTestView.ReadingData], todayDate: String, hour: Int, minute: Int) { + guard let todayReadingData = readingData.first(where: { $0.date == todayDate }) else { + print("오늘 독서관련 데이터를 불러오지 못했어요") + return + } + + guard todayReadingData.targetPages != nil, (todayReadingData.todayReadPages == nil || todayReadingData.todayReadPages == 0) else { + print("이미 오늘 페이지를 읽었거나 읽기로 계획한 날이 아니라 알람을 셋팅할 수 없어요") + return + } + + let content = UNMutableNotificationContent() + content.title = "리마인더 알람" + content.body = "오늘 완독하지 않았어요! 완독이가 물어버릴거에요 🥎 왕왕" + content.sound = .default + + + // 내가 제공한 오늘 날짜(todayDate)에 맞는 연,월,일에 알람이 트리거 되도록 설정 + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + guard let date = dateFormatter.date(from: todayDate) else { + print("오늘 날짜를 유효하게 받아오지 못했어요") + return + } + + let calendar = Calendar.current + let day = calendar.component(.day, from: date) + let month = calendar.component(.month, from: date) + let year = calendar.component(.year, from: date) + + var dateComponents = DateComponents() + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + dateComponents.hour = hour + dateComponents.minute = minute + + // 설정대로 트리거, 요청 셋팅 + let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) + let request = UNNotificationRequest(identifier: "reminderNoti", content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("리마인더 알람을 제대로 설정하지 못했어요. 에러: \(error)") + } else { + print("리마인더 알람을 설정했어요") + } + } + } + + // 1분 알람 설정 + func scheduleOneMinuteNotification() { + let content = UNMutableNotificationContent() + content.title = "1분 후 알람" + content.body = "설정하고 1분 후에 울렸어요" + content.sound = .default + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false) + let request = UNNotificationRequest(identifier: "oneMinuteNoti", content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("1분후 알람을 제대로 설정하지 못했어요. 에러: \(error)") + } else { + print("1분 후 알람을 설정했어요") + } + } + } + + + // 한번에 설정하는 함수 + func setupNotifications(readingData: [PushNotificationTestView.ReadingData], todayDate: String, hour: Int, minute: Int) { + + requestNotificationPermission() // 알람 요청 + scheduleSpecificTimeNotification(hour: hour, minute: minute) // 특정시간 알람 셋팅 + scheduleReminderNotification(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) // 리마인더 알람 셋팅 + scheduleOneMinuteNotification() // 1분 후 알람 셋팅 + } +} + +struct PushNotificationTestView: View { + @State private var notificationText: String = "" + let hour: Int = 16 + let minute: Int = 13 + let todayDate = "2024-11-08" // 내가 오늘이라고 테스팅을 위해 사용할 날짜 (리마인더 알람에 사용됨) + // 8일(오늘날짜) 가 아닌 날짜로 설정하면 제대로 리마인더 알람이 셋팅되지 않아요. + + // 더미데이터 + struct ReadingData { + let date: String + var todayReadPages: Int? + var targetPages: Int? + var currentPage: Int? + } + + + @State private var readingData: [ReadingData] = [ + ReadingData(date: "2024-11-01", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 + ReadingData(date: "2024-11-02", todayReadPages: 8, targetPages: 10, currentPage: 8), + ReadingData(date: "2024-11-03", todayReadPages: 15, targetPages: 20, currentPage: 23), + ReadingData(date: "2024-11-04", todayReadPages: 0, targetPages: 30, currentPage: 23), // 읽기로 했지만 안읽음 + ReadingData(date: "2024-11-05", todayReadPages: 12, targetPages: 40, currentPage: 35), + ReadingData(date: "2024-11-06", todayReadPages: 10, targetPages: 50, currentPage: 45), + ReadingData(date: "2024-11-07", todayReadPages: nil, targetPages: 60, currentPage: 45), // 읽기로 했지만 안읽음 + ReadingData(date: "2024-11-08", todayReadPages: nil, targetPages: 10, currentPage: 45), // 안읽기로 한 날 + ReadingData(date: "2024-11-09", todayReadPages: 14, targetPages: 70, currentPage: 59), + ReadingData(date: "2024-11-10", todayReadPages: 7, targetPages: 80, currentPage: 66), + ReadingData(date: "2024-11-11", todayReadPages: 13, targetPages: 90, currentPage: 79), + ReadingData(date: "2024-11-12", todayReadPages: 10, targetPages: 100, currentPage: 89), + ReadingData(date: "2024-11-13", todayReadPages: 15, targetPages: 110, currentPage: 104), + ReadingData(date: "2024-11-14", todayReadPages: 7, targetPages: 120, currentPage: 111), + ReadingData(date: "2024-11-15", todayReadPages: nil, targetPages: nil, currentPage: 111), // 안읽기로 한 날 + ReadingData(date: "2024-11-16", todayReadPages: 12, targetPages: 130, currentPage: 123), + ReadingData(date: "2024-11-17", todayReadPages: 0, targetPages: 140, currentPage: 123), // 오늘로 가정, 오늘 안읽음 + ReadingData(date: "2024-11-18", todayReadPages: nil, targetPages: 150, currentPage: nil), + ReadingData(date: "2024-11-19", todayReadPages: nil, targetPages: 160, currentPage: nil), // 읽기로 했지만 안읽음 + ReadingData(date: "2024-11-20", todayReadPages: nil, targetPages: 170, currentPage: nil), + ReadingData(date: "2024-11-21", todayReadPages: nil, targetPages: 180, currentPage: nil), + ReadingData(date: "2024-11-22", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 + ReadingData(date: "2024-11-23", todayReadPages: nil, targetPages: 190, currentPage: nil), + ReadingData(date: "2024-11-24", todayReadPages: nil, targetPages: 200, currentPage: nil), + ReadingData(date: "2024-11-25", todayReadPages: nil, targetPages: 210, currentPage: nil), + ReadingData(date: "2024-11-26", todayReadPages: nil, targetPages: 220, currentPage: nil), // 읽기로 했지만 안읽음 + ReadingData(date: "2024-11-27", todayReadPages: nil, targetPages: 230, currentPage: nil), + ReadingData(date: "2024-11-28", todayReadPages: nil, targetPages: 240, currentPage: nil), + ReadingData(date: "2024-11-29", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 + ReadingData(date: "2024-11-30", todayReadPages: nil, targetPages: 250, currentPage: nil), + ReadingData(date: "2024-12-01", todayReadPages: nil, targetPages: 260, currentPage: nil), + ReadingData(date: "2024-12-02", todayReadPages: nil, targetPages: 270, currentPage: nil), + ReadingData(date: "2024-12-03", todayReadPages: nil, targetPages: 280, currentPage: nil), + ReadingData(date: "2024-12-04", todayReadPages: nil, targetPages: 290, currentPage: nil), // 읽기로 했지만 안읽음 + ReadingData(date: "2024-12-05", todayReadPages: nil, targetPages: 300, currentPage: nil), + ReadingData(date: "2024-12-06", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 + ReadingData(date: "2024-12-07", todayReadPages: nil, targetPages: 310, currentPage: nil), + ReadingData(date: "2024-12-08", todayReadPages: nil, targetPages: 320, currentPage: 320) // 완독일 12월 8일 + ] + + + var body: some View { + + + VStack { + Text(notificationText) + .font(.title) + + // 한번에 세개알람 설정하기 + Button( + action: { + NotificationManager.shared.setupNotifications(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) + }, + label: { + Text("알람 세개 동시설정") + .padding() + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(8.0) + } + ) + + Button( + action: { + NotificationManager.shared.requestNotificationPermission() // 알람 요청 + NotificationManager.shared.scheduleSpecificTimeNotification(hour: hour, minute: minute) // 특정시간 알람 셋팅 + }, + label: { + Text("특정 시간 알람 설정") + .padding() + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(8.0) + } + ) + + // 리마인더 알람 설정 + Button( + action: { + NotificationManager.shared.requestNotificationPermission() // 알람 요청 + NotificationManager.shared.scheduleReminderNotification(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) // 리마인더 알람 셋팅 + }, + label: { + Text("리마인더 알람 설정") + .padding() + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(8.0) + } + ) + + // 일분 후 알람 설정 + Button( + action: { + NotificationManager.shared.requestNotificationPermission() // 알람 요청 + NotificationManager.shared.scheduleOneMinuteNotification() // 1분 후 알람 셋팅 + }, + label: { + Text("일분 후 알람 설정") + .padding() + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(8.0) + } + ) + + } + } + + + +} + From fa38dc3f2d70c4f869b1961aa1326ac289461699 Mon Sep 17 00:00:00 2001 From: zaehorang Date: Tue, 12 Nov 2024 22:48:49 +0900 Subject: [PATCH 2/4] chore: #54 Delete image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용하지 않는 이미지를 제거합니다. --- .../CircleButtonFilled.imageset/1.png | Bin 1396 -> 0 bytes .../CircleButtonFilled.imageset/2.png | Bin 2596 -> 0 bytes .../CircleButtonFilled.imageset/3.png | Bin 3736 -> 0 bytes .../CircleButtonFilled.imageset/Contents.json | 23 ------------------ 4 files changed, 23 deletions(-) delete mode 100644 FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/1.png delete mode 100644 FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/2.png delete mode 100644 FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/3.png delete mode 100644 FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/Contents.json diff --git a/FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/1.png b/FiveGuyes/FiveGuyes/Resources/Assets.xcassets/CircleButtonFilled.imageset/1.png deleted file mode 100644 index 648f68fcd871c80786eade017329b831ec5db2a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1396 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 Date: Tue, 12 Nov 2024 23:06:57 +0900 Subject: [PATCH 3/4] feat: #54 Add notification manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 아침과 저녁에 사용자에게 노티를 보내는 기능을 추가합니다. --- FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj | 31 +- .../xcshareddata/swiftpm/Package.resolved | 14 + .../Sources/Models/NotificationManager.swift | 88 ++++++ .../Sources/Models/NotificationType.swift | 53 ++++ .../Models/ReadingScheduleCalculator.swift | 47 ++-- .../FiveGuyes/Sources/Models/UserBook.swift | 27 +- .../FiveGuyes/Sources/Stores/APIStore.swift | 3 - .../Sources/Stores/KeyboardObserver.swift | 2 +- .../Stores/PushNotificationTestView.swift | 264 ------------------ .../CompletionCelebrationView.swift | 2 +- .../BookProgress/DailyProgressView.swift | 22 +- .../Screen/BookSetting/FinishGoalView.swift | 16 +- .../Screen/Testing/ReadingTestView.swift | 184 ------------ 13 files changed, 256 insertions(+), 497 deletions(-) create mode 100644 FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 FiveGuyes/FiveGuyes/Sources/Models/NotificationManager.swift create mode 100644 FiveGuyes/FiveGuyes/Sources/Models/NotificationType.swift delete mode 100644 FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift delete mode 100644 FiveGuyes/FiveGuyes/Sources/Views/Screen/Testing/ReadingTestView.swift diff --git a/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj b/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj index 81db57e..2f7de98 100644 --- a/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj +++ b/FiveGuyes/FiveGuyes.xcodeproj/project.pbxproj @@ -18,8 +18,7 @@ 1A27D9662CDB779000D1E14D /* TotalCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A27D9652CDB779000D1E14D /* TotalCalendarView.swift */; }; 1A54142B2CD9FEF400283FBD /* BookSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A54142A2CD9FEF400283FBD /* BookSearchView.swift */; }; 1AA14A782CDDA30D00B763A6 /* TotalCalendarTextBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA14A772CDDA30D00B763A6 /* TotalCalendarTextBubble.swift */; }; - 1AA14A7A2CDDD96200B763A6 /* PushNotificationTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */; }; - 24360D462CD8A2F800E83D2B /* EmptyDataMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D452CD8A2F800E83D2B /* EmptyDataMainView.swift */; }; + 1AA14A7A2CDDD96200B763A6 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA14A792CDDD96200B763A6 /* NotificationManager.swift */; }; 24360D482CD8BAF100E83D2B /* EmptyNotiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D472CD8BAF100E83D2B /* EmptyNotiView.swift */; }; 24360D4A2CD8BE3C00E83D2B /* FinishGoalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D492CD8BE3C00E83D2B /* FinishGoalView.swift */; }; 24360D532CD9F3AF00E83D2B /* DailyProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24360D522CD9F3AF00E83D2B /* DailyProgressView.swift */; }; @@ -41,6 +40,7 @@ 26EBDE262CDE3C6000B3A2BC /* BookSettingInputModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EBDE252CDE3C6000B3A2BC /* BookSettingInputModel.swift */; }; 26EBDE2A2CDF33D900B3A2BC /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EBDE292CDF33D900B3A2BC /* Date+Extension.swift */; }; 26EBDE2C2CE1341800B3A2BC /* UserBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EBDE2B2CE1341800B3A2BC /* UserBook.swift */; }; + 26EBDE2F2CE3856900B3A2BC /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EBDE2E2CE3856900B3A2BC /* NotificationType.swift */; }; 26F19A682CD9EFD800F41D6D /* MainHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A672CD9EFD800F41D6D /* MainHomeView.swift */; }; 26F19A6A2CD9FE5C00F41D6D /* WeeklyReadingProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A692CD9FE5C00F41D6D /* WeeklyReadingProgressView.swift */; }; 26F19A6C2CDA0A2B00F41D6D /* CompletionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A6B2CDA0A2B00F41D6D /* CompletionListView.swift */; }; @@ -49,7 +49,6 @@ 26F19A742CDB271E00F41D6D /* NavigationRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A732CDB271E00F41D6D /* NavigationRootView.swift */; }; 26F19A782CDB5D0100F41D6D /* BookDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A772CDB5D0100F41D6D /* BookDetails.swift */; }; 26F19A7A2CDB5F3900F41D6D /* ReadingScheduleCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A792CDB5F3900F41D6D /* ReadingScheduleCalculator.swift */; }; - 26F19A822CDBE3A600F41D6D /* ReadingTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F19A812CDBE3A600F41D6D /* ReadingTestView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -65,7 +64,7 @@ 1A27D9652CDB779000D1E14D /* TotalCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCalendarView.swift; sourceTree = ""; }; 1A54142A2CD9FEF400283FBD /* BookSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BookSearchView.swift; path = FiveGuyes/Sources/Views/Screen/BookSetting/BookSearch/BookSearchView.swift; sourceTree = SOURCE_ROOT; }; 1AA14A772CDDA30D00B763A6 /* TotalCalendarTextBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCalendarTextBubble.swift; sourceTree = ""; }; - 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationTestView.swift; sourceTree = ""; }; + 1AA14A792CDDD96200B763A6 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 24360D452CD8A2F800E83D2B /* EmptyDataMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyDataMainView.swift; sourceTree = ""; }; 24360D472CD8BAF100E83D2B /* EmptyNotiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyNotiView.swift; sourceTree = ""; }; 24360D492CD8BE3C00E83D2B /* FinishGoalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinishGoalView.swift; sourceTree = ""; }; @@ -89,6 +88,7 @@ 26EBDE252CDE3C6000B3A2BC /* BookSettingInputModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookSettingInputModel.swift; sourceTree = ""; }; 26EBDE292CDF33D900B3A2BC /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 26EBDE2B2CE1341800B3A2BC /* UserBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBook.swift; sourceTree = ""; }; + 26EBDE2E2CE3856900B3A2BC /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = ""; }; 26F19A672CD9EFD800F41D6D /* MainHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeView.swift; sourceTree = ""; }; 26F19A692CD9FE5C00F41D6D /* WeeklyReadingProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeeklyReadingProgressView.swift; sourceTree = ""; }; 26F19A6B2CDA0A2B00F41D6D /* CompletionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionListView.swift; sourceTree = ""; }; @@ -97,7 +97,6 @@ 26F19A732CDB271E00F41D6D /* NavigationRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootView.swift; sourceTree = ""; }; 26F19A772CDB5D0100F41D6D /* BookDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookDetails.swift; sourceTree = ""; }; 26F19A792CDB5F3900F41D6D /* ReadingScheduleCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadingScheduleCalculator.swift; sourceTree = ""; }; - 26F19A812CDBE3A600F41D6D /* ReadingTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadingTestView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -146,6 +145,7 @@ 261714D32CDD27C900A3241D /* Config.xcconfig */, 26890B932CAE811A008DFF49 /* FiveGuyes */, 26890B922CAE811A008DFF49 /* Products */, + 26EBDE2D2CE3307C00B3A2BC /* Recovered References */, ); sourceTree = ""; }; @@ -222,7 +222,6 @@ 26D852FB2CCE40BC0016239A /* Screen */ = { isa = PBXGroup; children = ( - 26F19A832CDC970D00F41D6D /* Testing */, 26F19A662CD9EF9400F41D6D /* Main */, 1A27D95E2CDA155B00D1E14D /* BookSetting */, 24360D4F2CD9F2D800E83D2B /* BookProgress */, @@ -257,7 +256,6 @@ 264440542CD8E0100031A290 /* KeyboardObserver.swift */, 26F19A712CDB267600F41D6D /* NavigationCoordinator.swift */, 26EBDE252CDE3C6000B3A2BC /* BookSettingInputModel.swift */, - 1AA14A792CDDD96200B763A6 /* PushNotificationTestView.swift */, ); path = Stores; sourceTree = ""; @@ -269,6 +267,8 @@ 26F19A772CDB5D0100F41D6D /* BookDetails.swift */, 26EBDE2B2CE1341800B3A2BC /* UserBook.swift */, 26F19A792CDB5F3900F41D6D /* ReadingScheduleCalculator.swift */, + 26EBDE2E2CE3856900B3A2BC /* NotificationType.swift */, + 1AA14A792CDDD96200B763A6 /* NotificationManager.swift */, ); path = Models; sourceTree = ""; @@ -305,20 +305,20 @@ path = BookSearch; sourceTree = ""; }; - 26F19A662CD9EF9400F41D6D /* Main */ = { + 26EBDE2D2CE3307C00B3A2BC /* Recovered References */ = { isa = PBXGroup; children = ( - 26F19A672CD9EFD800F41D6D /* MainHomeView.swift */, + 24360D452CD8A2F800E83D2B /* EmptyDataMainView.swift */, ); - path = Main; + name = "Recovered References"; sourceTree = ""; }; - 26F19A832CDC970D00F41D6D /* Testing */ = { + 26F19A662CD9EF9400F41D6D /* Main */ = { isa = PBXGroup; children = ( - 26F19A812CDBE3A600F41D6D /* ReadingTestView.swift */, + 26F19A672CD9EFD800F41D6D /* MainHomeView.swift */, ); - path = Testing; + path = Main; sourceTree = ""; }; /* End PBXGroup section */ @@ -399,7 +399,6 @@ files = ( 1A54142B2CD9FEF400283FBD /* BookSearchView.swift in Sources */, 26F19A782CDB5D0100F41D6D /* BookDetails.swift in Sources */, - 26F19A822CDBE3A600F41D6D /* ReadingTestView.swift in Sources */, 1A010EAE2CD8A86E00FBE3B3 /* APIStore.swift in Sources */, 24A2063F2CDB180000964FBB /* CompletionCalendarView.swift in Sources */, 1A010EBE2CD8DA2400FBE3B3 /* BookListView.swift in Sources */, @@ -421,8 +420,7 @@ 24360D4A2CD8BE3C00E83D2B /* FinishGoalView.swift in Sources */, 26F19A682CD9EFD800F41D6D /* MainHomeView.swift in Sources */, 26F19A742CDB271E00F41D6D /* NavigationRootView.swift in Sources */, - 24360D462CD8A2F800E83D2B /* EmptyDataMainView.swift in Sources */, - 1AA14A7A2CDDD96200B763A6 /* PushNotificationTestView.swift in Sources */, + 1AA14A7A2CDDD96200B763A6 /* NotificationManager.swift in Sources */, 264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */, 264440502CD8A3AC0031A290 /* CompletionReviewView.swift in Sources */, 26890B952CAE811A008DFF49 /* FiveGuyesApp.swift in Sources */, @@ -432,6 +430,7 @@ 264440572CD8E0D20031A290 /* CustomTextEditorStyle.swift in Sources */, 264440552CD8E0100031A290 /* KeyboardObserver.swift in Sources */, 26F19A6C2CDA0A2B00F41D6D /* CompletionListView.swift in Sources */, + 26EBDE2F2CE3856900B3A2BC /* NotificationType.swift in Sources */, 1A27D9662CDB779000D1E14D /* TotalCalendarView.swift in Sources */, 2644404E2CD8A39E0031A290 /* CompletionCelebrationView.swift in Sources */, 26EBDE262CDE3C6000B3A2BC /* BookSettingInputModel.swift in Sources */, diff --git a/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..17b1024 --- /dev/null +++ b/FiveGuyes/FiveGuyes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swiftlintplugins", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", + "state" : { + "revision" : "7c80ce6f142164b0201871e580b021d1b2c69804", + "version" : "0.57.0" + } + } + ], + "version" : 2 +} diff --git a/FiveGuyes/FiveGuyes/Sources/Models/NotificationManager.swift b/FiveGuyes/FiveGuyes/Sources/Models/NotificationManager.swift new file mode 100644 index 0000000..5708dfa --- /dev/null +++ b/FiveGuyes/FiveGuyes/Sources/Models/NotificationManager.swift @@ -0,0 +1,88 @@ +// +// NotificationManager.swift +// FiveGuyes +// +// Created by Shim Hyeonhee on 11/8/24. +// + +import UserNotifications + +final class NotificationManager { + private let notificationCenter = UNUserNotificationCenter.current() + private var isGranted: Bool = false + + func setupNotifications(notificationType: NotificationType) async { + await requestAuthorization() + + if isGranted { + await scheduleReminderNotification(notificationType: notificationType) + } + } + + /// 요청한 Noticifation을 모두 지우는 함수 + func clearRequests() { + notificationCenter.removeAllPendingNotificationRequests() + } + + /// Notification 권한 요청 함수 + private func requestAuthorization() async { + do { + try await notificationCenter + .requestAuthorization(options: [.sound, .badge, .alert]) + } catch { + print("❌ NotificationManager/requestAuthorization: \(error.localizedDescription)") + } + + await getCurrentSettings() + } + + /// 현재 Notification 권한 설정을 가져오는 함수 + private func getCurrentSettings() async { + let currentSettings = await notificationCenter.notificationSettings() + + isGranted = (currentSettings.authorizationStatus == .authorized) + } + + private func scheduleReminderNotification(notificationType: NotificationType) async { + // dateContent가 nil일 경우 알림을 보내지 않음 + guard let date = notificationType.dateContent() else { + print("❌ NotificationManager: 다음 읽기 날짜가 없어 알림을 생성하지 않습니다.") + return + } + + let dateComponents = makeDateComponents(date: date, notificationType) + let content = makeNotificationContent(notificationType) + + let identifier = notificationType.identifier() + + // 설정대로 트리거, 요청 셋팅 + let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) + let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) + + do { + try await notificationCenter.add(request) + print("💯 노티 설정 완료") + } catch { + print("❌ NotificationManager/schedule: \(error.localizedDescription)") + } + } + + private func makeDateComponents(date: Date, _ notificationType: NotificationType) -> DateComponents { + let calendar = Calendar.current + let day = calendar.component(.day, from: date) + let month = calendar.component(.month, from: date) + let year = calendar.component(.year, from: date) + let (hour, minute) = notificationType.timeContent() + print("💯노티 설정: \(date) \(hour): \(minute)") + return DateComponents(year: year, month: month, day: day, hour: hour, minute: minute) + } + + private func makeNotificationContent(_ notificationType: NotificationType) -> UNMutableNotificationContent { + let content = UNMutableNotificationContent() + let (title, body) = notificationType.descriptionContent() + content.title = title + content.body = body + + return content + } +} diff --git a/FiveGuyes/FiveGuyes/Sources/Models/NotificationType.swift b/FiveGuyes/FiveGuyes/Sources/Models/NotificationType.swift new file mode 100644 index 0000000..e38ca9b --- /dev/null +++ b/FiveGuyes/FiveGuyes/Sources/Models/NotificationType.swift @@ -0,0 +1,53 @@ +// +// NotificationType.swift +// FiveGuyes +// +// Created by zaehorang on 11/12/24. +// + +import Foundation + +enum NotificationType { + case morning(readingBook: UserBook) + case night(readingBook: UserBook) + + func descriptionContent() -> (title: String, body: String) { + switch self { + case .morning(let readingBook): + let title = "오늘의 한입, 준비됐나요?" + let body = "오늘은 \(readingBook.findNextReadingPagesPerDay())페이지만 읽으면 돼요 멍멍!" + return (title, body) + + case .night: + let title = "오늘의 한입독서를 놓치고 계신가요?" + let body = "오늘 완독하지 않았어요!\n완독이가 물어버릴거에요 🥎 왕왕" + return (title, body) + } + } + + func dateContent() -> Date? { + switch self { + case .morning(let readingBook), .night(let readingBook): + return readingBook.findNextReadingDay() + } + } + + func timeContent() -> (hour: Int, minute: Int) { + switch self { + case .morning: + return (11, 0) + case .night: + return (23, 0) + } + } + + /// 고유 identifier 생성 메서드 + func identifier() -> String { + switch self { + case .morning(let readingBook): + return "\(readingBook.book.title)-morning" + case .night(let readingBook): + return "\(readingBook.book.title)-night" + } + } +} diff --git a/FiveGuyes/FiveGuyes/Sources/Models/ReadingScheduleCalculator.swift b/FiveGuyes/FiveGuyes/Sources/Models/ReadingScheduleCalculator.swift index bb5d789..14af7ed 100644 --- a/FiveGuyes/FiveGuyes/Sources/Models/ReadingScheduleCalculator.swift +++ b/FiveGuyes/FiveGuyes/Sources/Models/ReadingScheduleCalculator.swift @@ -24,8 +24,7 @@ struct ReadingScheduleCalculator { // MARK: 첫날을 기준으로 읽어야하는 페이지를 할당하는 메서드 (초기 페이지 계산) func calculateInitialDailyTargets(for currentReadingBook: UserBook) { - let pagesPerDay = firstCalculatePagesPerDay(for: currentReadingBook) - let remainderPages = firstCalculateRemainderPages(for: currentReadingBook) + let (pagesPerDay, remainderPages) = firstCalculatePagesPerDay(for: currentReadingBook) var targetDate = currentReadingBook.book.startDate var remainderOffset = remainderPages @@ -78,7 +77,7 @@ struct ReadingScheduleCalculator { } } - //MARK: 더 읽거나, 덜 읽으면 이후 날짜의 할당량을 다시 계산한다. + // MARK: 더 읽거나, 덜 읽으면 이후 날짜의 할당량을 다시 계산한다. func adjustFutureTargets(for currentReadingBook: UserBook, from date: Date) { let totalRemainingPages = calculateRemainingPages(for: currentReadingBook) print("❌: \(totalRemainingPages)") @@ -126,16 +125,10 @@ struct ReadingScheduleCalculator { func reassignPagesFromLastReadDate(for currentReadingBook: UserBook) { // 이미 읽었으면 재분배 x if hasReadPagesToday(for: currentReadingBook) { return } - - // 몇 페이지 남음? - let totalRemainingPages = calculateRemainingPages(for: currentReadingBook) - print("❌re: \(totalRemainingPages)") - // 오늘부터 며칠 남음? - let remainingDays = calculateRemainingReadingDays(for: currentReadingBook) - print("🐶re: \(remainingDays)") - // 남은 페이지와 날짜를 기준으로 새롭게 할당량 계산 - let pagesPerDay = totalRemainingPages / remainingDays - var remainderOffset = totalRemainingPages % remainingDays + + // 남은 페이지와 일수를 기준으로 새롭게 할당량 계산 + let (pagesPerDay, remainderPages) = calculatePagesPerDay(for: currentReadingBook) + var remainderOffset = remainderPages var cumulativePages = currentReadingBook.lastPagesRead var targetDate = Date() // 오늘 날짜부터 새로 할당 시작 @@ -187,16 +180,12 @@ struct ReadingScheduleCalculator { } // 하루에 몇 페이지 읽는지 계산 - func firstCalculatePagesPerDay(for currentReadingBook: UserBook) -> Int { - let totalReadingDays = firstCalculateTotalReadingDays(for: currentReadingBook) - return currentReadingBook.book.totalPages / totalReadingDays - } - - - // 하루에 몇 페이지 읽는지 계산하고 딱 떨어지지 않는 페이지 수 구하는 메서드 - func firstCalculateRemainderPages(for currentReadingBook: UserBook) -> Int { + func firstCalculatePagesPerDay(for currentReadingBook: UserBook) -> (pagesPerDay: Int, remainder: Int) { let totalReadingDays = firstCalculateTotalReadingDays(for: currentReadingBook) - return currentReadingBook.book.totalPages % totalReadingDays + let pagesPerDay = currentReadingBook.book.totalPages / totalReadingDays + let remainder = currentReadingBook.book.totalPages % totalReadingDays + + return (pagesPerDay, remainder) } // MARK: - 남은 양을 다시 계산할 때 사용하는 메서드 @@ -220,6 +209,19 @@ struct ReadingScheduleCalculator { return remainingDays } + // 남은 페이지와 날짜를 기반으로 일일 할당량을 계산하는 메서드 + func calculatePagesPerDay(for currentReadingBook: UserBook) -> (pagesPerDay: Int, remainder: Int) { + let totalRemainingPages = calculateRemainingPages(for: currentReadingBook) + let remainingDays = calculateRemainingReadingDays(for: currentReadingBook) + + let pagesPerDay = totalRemainingPages / remainingDays + let remainder = totalRemainingPages % remainingDays + + print("❌읽는 중: \(totalRemainingPages)") + print("🐶읽는 중: \(remainingDays)") + + return (pagesPerDay, remainder) + } // 특정 날의 묙표량과 실제 읽은 페이지의 수를 가져오는 메서드 func getReadingRecord(for currentReadingBook: UserBook, for date: Date) -> ReadingRecord? { @@ -228,4 +230,3 @@ struct ReadingScheduleCalculator { return currentReadingBook.readingRecords[dateKey] } } - diff --git a/FiveGuyes/FiveGuyes/Sources/Models/UserBook.swift b/FiveGuyes/FiveGuyes/Sources/Models/UserBook.swift index 1727296..a7b032f 100644 --- a/FiveGuyes/FiveGuyes/Sources/Models/UserBook.swift +++ b/FiveGuyes/FiveGuyes/Sources/Models/UserBook.swift @@ -30,7 +30,9 @@ final class UserBook { init(book: BookDetails) { self.book = book } - +} + +extension UserBook { func markAsCompleted(review: String) { // 책을 완독 상태로 설정 book.targetEndDate = Date() @@ -53,4 +55,27 @@ final class UserBook { } return readingRecords.values.filter { $0.pagesRead > 0 }.count } + + /// 오늘 이후 다음 읽기 예정일을 반환하는 메서드 + func findNextReadingDay() -> Date? { + let today = lastReadDate ?? Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + let todayString = dateFormatter.string(from: today) + print("today⭐️: \(today)") + + // 오늘 이후 날짜들 중 비독서일을 제외한 첫 읽기 예정일을 찾음 + for dateString in readingRecords.keys.sorted() + where dateString > todayString { + return dateFormatter.date(from: dateString) + } + // 모든 읽기 예정일이 지난 경우 nil 반환 + return nil + } + + func findNextReadingPagesPerDay() -> Int { + let readingScheduleCalculator = ReadingScheduleCalculator() + + return readingScheduleCalculator.calculatePagesPerDay(for: self).pagesPerDay + } } diff --git a/FiveGuyes/FiveGuyes/Sources/Stores/APIStore.swift b/FiveGuyes/FiveGuyes/Sources/Stores/APIStore.swift index 1621f1a..3799c86 100644 --- a/FiveGuyes/FiveGuyes/Sources/Stores/APIStore.swift +++ b/FiveGuyes/FiveGuyes/Sources/Stores/APIStore.swift @@ -41,10 +41,7 @@ class APIStore { } let (data, _) = try await URLSession.shared.data(from: url) - print(data) let bookDetailResponse = try JSONDecoder().decode(BookDetailResponse.self, from: data) - print(bookDetailResponse) - print(bookDetailResponse.item?.first?.subInfo?.itemPage) return bookDetailResponse.item?.first?.subInfo?.itemPage ?? 0 } } diff --git a/FiveGuyes/FiveGuyes/Sources/Stores/KeyboardObserver.swift b/FiveGuyes/FiveGuyes/Sources/Stores/KeyboardObserver.swift index e9b4ca5..746112a 100644 --- a/FiveGuyes/FiveGuyes/Sources/Stores/KeyboardObserver.swift +++ b/FiveGuyes/FiveGuyes/Sources/Stores/KeyboardObserver.swift @@ -7,7 +7,7 @@ import UIKit -//MARK: - 키보드가 올라오는 시점을 알기 위한 모델 +// MARK: - 키보드가 올라오는 시점을 알기 위한 모델 final class KeyboardObserver: ObservableObject { @Published var keyboardIsVisible: Bool = false diff --git a/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift b/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift deleted file mode 100644 index 8351e21..0000000 --- a/FiveGuyes/FiveGuyes/Sources/Stores/PushNotificationTestView.swift +++ /dev/null @@ -1,264 +0,0 @@ -// -// PushNotificationTest.swift -// FiveGuyes -// -// Created by Shim Hyeonhee on 11/8/24. -// - -import SwiftUI -import UserNotifications - - - -// 노티피케이션 테스트용 매니저 -class NotificationManager { - // 싱글톤 설정 - - static let shared = NotificationManager() - - /// 초기 알람 설정을 위한 권한 호출 - func requestNotificationPermission() { - let options: UNAuthorizationOptions = [.alert, .sound, .badge] - UNUserNotificationCenter.current().requestAuthorization(options: options) { suceess, error in - if let error { - print("알람 권한 호출에 문제가 있어요 에러: \(error)") - } else { - print("성공적으료 알람을 위한 권한을 호출했어요") - } - } - } - - // 특정 시간 알람 - func scheduleSpecificTimeNotification(hour: Int, minute: Int) { - let content = UNMutableNotificationContent() - content.title = "특정시간알람" - content.body = "오늘도 꼭 완독하세요!" - content.sound = .default - - var dateComponents = DateComponents() - dateComponents.hour = hour - dateComponents.minute = minute - - let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) - let request = UNNotificationRequest(identifier: "specificTimeNoti", content: content, trigger: trigger) - - UNUserNotificationCenter.current().add(request) { error in - if let error = error { - print("특정 시간 알람이 설정 되지 않았어요 \(error)") - } else { - print("특정 시간 알람을 설정했어요") - } - } - } - - - func scheduleReminderNotification(readingData: [PushNotificationTestView.ReadingData], todayDate: String, hour: Int, minute: Int) { - guard let todayReadingData = readingData.first(where: { $0.date == todayDate }) else { - print("오늘 독서관련 데이터를 불러오지 못했어요") - return - } - - guard todayReadingData.targetPages != nil, (todayReadingData.todayReadPages == nil || todayReadingData.todayReadPages == 0) else { - print("이미 오늘 페이지를 읽었거나 읽기로 계획한 날이 아니라 알람을 셋팅할 수 없어요") - return - } - - let content = UNMutableNotificationContent() - content.title = "리마인더 알람" - content.body = "오늘 완독하지 않았어요! 완독이가 물어버릴거에요 🥎 왕왕" - content.sound = .default - - - // 내가 제공한 오늘 날짜(todayDate)에 맞는 연,월,일에 알람이 트리거 되도록 설정 - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" - guard let date = dateFormatter.date(from: todayDate) else { - print("오늘 날짜를 유효하게 받아오지 못했어요") - return - } - - let calendar = Calendar.current - let day = calendar.component(.day, from: date) - let month = calendar.component(.month, from: date) - let year = calendar.component(.year, from: date) - - var dateComponents = DateComponents() - dateComponents.year = year - dateComponents.month = month - dateComponents.day = day - dateComponents.hour = hour - dateComponents.minute = minute - - // 설정대로 트리거, 요청 셋팅 - let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) - let request = UNNotificationRequest(identifier: "reminderNoti", content: content, trigger: trigger) - - UNUserNotificationCenter.current().add(request) { error in - if let error = error { - print("리마인더 알람을 제대로 설정하지 못했어요. 에러: \(error)") - } else { - print("리마인더 알람을 설정했어요") - } - } - } - - // 1분 알람 설정 - func scheduleOneMinuteNotification() { - let content = UNMutableNotificationContent() - content.title = "1분 후 알람" - content.body = "설정하고 1분 후에 울렸어요" - content.sound = .default - - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false) - let request = UNNotificationRequest(identifier: "oneMinuteNoti", content: content, trigger: trigger) - - UNUserNotificationCenter.current().add(request) { error in - if let error = error { - print("1분후 알람을 제대로 설정하지 못했어요. 에러: \(error)") - } else { - print("1분 후 알람을 설정했어요") - } - } - } - - - // 한번에 설정하는 함수 - func setupNotifications(readingData: [PushNotificationTestView.ReadingData], todayDate: String, hour: Int, minute: Int) { - - requestNotificationPermission() // 알람 요청 - scheduleSpecificTimeNotification(hour: hour, minute: minute) // 특정시간 알람 셋팅 - scheduleReminderNotification(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) // 리마인더 알람 셋팅 - scheduleOneMinuteNotification() // 1분 후 알람 셋팅 - } -} - -struct PushNotificationTestView: View { - @State private var notificationText: String = "" - let hour: Int = 16 - let minute: Int = 13 - let todayDate = "2024-11-08" // 내가 오늘이라고 테스팅을 위해 사용할 날짜 (리마인더 알람에 사용됨) - // 8일(오늘날짜) 가 아닌 날짜로 설정하면 제대로 리마인더 알람이 셋팅되지 않아요. - - // 더미데이터 - struct ReadingData { - let date: String - var todayReadPages: Int? - var targetPages: Int? - var currentPage: Int? - } - - - @State private var readingData: [ReadingData] = [ - ReadingData(date: "2024-11-01", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 - ReadingData(date: "2024-11-02", todayReadPages: 8, targetPages: 10, currentPage: 8), - ReadingData(date: "2024-11-03", todayReadPages: 15, targetPages: 20, currentPage: 23), - ReadingData(date: "2024-11-04", todayReadPages: 0, targetPages: 30, currentPage: 23), // 읽기로 했지만 안읽음 - ReadingData(date: "2024-11-05", todayReadPages: 12, targetPages: 40, currentPage: 35), - ReadingData(date: "2024-11-06", todayReadPages: 10, targetPages: 50, currentPage: 45), - ReadingData(date: "2024-11-07", todayReadPages: nil, targetPages: 60, currentPage: 45), // 읽기로 했지만 안읽음 - ReadingData(date: "2024-11-08", todayReadPages: nil, targetPages: 10, currentPage: 45), // 안읽기로 한 날 - ReadingData(date: "2024-11-09", todayReadPages: 14, targetPages: 70, currentPage: 59), - ReadingData(date: "2024-11-10", todayReadPages: 7, targetPages: 80, currentPage: 66), - ReadingData(date: "2024-11-11", todayReadPages: 13, targetPages: 90, currentPage: 79), - ReadingData(date: "2024-11-12", todayReadPages: 10, targetPages: 100, currentPage: 89), - ReadingData(date: "2024-11-13", todayReadPages: 15, targetPages: 110, currentPage: 104), - ReadingData(date: "2024-11-14", todayReadPages: 7, targetPages: 120, currentPage: 111), - ReadingData(date: "2024-11-15", todayReadPages: nil, targetPages: nil, currentPage: 111), // 안읽기로 한 날 - ReadingData(date: "2024-11-16", todayReadPages: 12, targetPages: 130, currentPage: 123), - ReadingData(date: "2024-11-17", todayReadPages: 0, targetPages: 140, currentPage: 123), // 오늘로 가정, 오늘 안읽음 - ReadingData(date: "2024-11-18", todayReadPages: nil, targetPages: 150, currentPage: nil), - ReadingData(date: "2024-11-19", todayReadPages: nil, targetPages: 160, currentPage: nil), // 읽기로 했지만 안읽음 - ReadingData(date: "2024-11-20", todayReadPages: nil, targetPages: 170, currentPage: nil), - ReadingData(date: "2024-11-21", todayReadPages: nil, targetPages: 180, currentPage: nil), - ReadingData(date: "2024-11-22", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 - ReadingData(date: "2024-11-23", todayReadPages: nil, targetPages: 190, currentPage: nil), - ReadingData(date: "2024-11-24", todayReadPages: nil, targetPages: 200, currentPage: nil), - ReadingData(date: "2024-11-25", todayReadPages: nil, targetPages: 210, currentPage: nil), - ReadingData(date: "2024-11-26", todayReadPages: nil, targetPages: 220, currentPage: nil), // 읽기로 했지만 안읽음 - ReadingData(date: "2024-11-27", todayReadPages: nil, targetPages: 230, currentPage: nil), - ReadingData(date: "2024-11-28", todayReadPages: nil, targetPages: 240, currentPage: nil), - ReadingData(date: "2024-11-29", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 - ReadingData(date: "2024-11-30", todayReadPages: nil, targetPages: 250, currentPage: nil), - ReadingData(date: "2024-12-01", todayReadPages: nil, targetPages: 260, currentPage: nil), - ReadingData(date: "2024-12-02", todayReadPages: nil, targetPages: 270, currentPage: nil), - ReadingData(date: "2024-12-03", todayReadPages: nil, targetPages: 280, currentPage: nil), - ReadingData(date: "2024-12-04", todayReadPages: nil, targetPages: 290, currentPage: nil), // 읽기로 했지만 안읽음 - ReadingData(date: "2024-12-05", todayReadPages: nil, targetPages: 300, currentPage: nil), - ReadingData(date: "2024-12-06", todayReadPages: nil, targetPages: nil, currentPage: nil), // 안읽기로 한 날 - ReadingData(date: "2024-12-07", todayReadPages: nil, targetPages: 310, currentPage: nil), - ReadingData(date: "2024-12-08", todayReadPages: nil, targetPages: 320, currentPage: 320) // 완독일 12월 8일 - ] - - - var body: some View { - - - VStack { - Text(notificationText) - .font(.title) - - // 한번에 세개알람 설정하기 - Button( - action: { - NotificationManager.shared.setupNotifications(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) - }, - label: { - Text("알람 세개 동시설정") - .padding() - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(8.0) - } - ) - - Button( - action: { - NotificationManager.shared.requestNotificationPermission() // 알람 요청 - NotificationManager.shared.scheduleSpecificTimeNotification(hour: hour, minute: minute) // 특정시간 알람 셋팅 - }, - label: { - Text("특정 시간 알람 설정") - .padding() - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(8.0) - } - ) - - // 리마인더 알람 설정 - Button( - action: { - NotificationManager.shared.requestNotificationPermission() // 알람 요청 - NotificationManager.shared.scheduleReminderNotification(readingData: readingData, todayDate: todayDate, hour: hour, minute: minute) // 리마인더 알람 셋팅 - }, - label: { - Text("리마인더 알람 설정") - .padding() - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(8.0) - } - ) - - // 일분 후 알람 설정 - Button( - action: { - NotificationManager.shared.requestNotificationPermission() // 알람 요청 - NotificationManager.shared.scheduleOneMinuteNotification() // 1분 후 알람 셋팅 - }, - label: { - Text("일분 후 알람 설정") - .padding() - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(8.0) - } - ) - - } - } - - - -} - diff --git a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookCompletion/CompletionCelebrationView.swift b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookCompletion/CompletionCelebrationView.swift index f53b576..bb52dca 100644 --- a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookCompletion/CompletionCelebrationView.swift +++ b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookCompletion/CompletionCelebrationView.swift @@ -105,7 +105,7 @@ struct CompletionCelebrationView: View { let startDateText = book.startDate.toKoreanDateString() // TODO: 완독을 수정할 수도 있기 때문에 완독 날짜가 바뀔 수 있음, 그래서 완독 날짜는 최종에서 업데이트하고 여기서는 오늘 날짜로 보여주기 let endDateText = Date().toKoreanDateString() - let pagesPerDay = readingScheduleCalculator.firstCalculatePagesPerDay(for: userBook) + let pagesPerDay = readingScheduleCalculator.firstCalculatePagesPerDay(for: userBook).pagesPerDay let totalReadingDays = readingScheduleCalculator.firstCalculateTotalReadingDays(for: userBook) return Text("\(startDateText)부터 \(endDateText)까지\n꾸준히 \(pagesPerDay)쪽씩 \(totalReadingDays)일동안 읽었어요 🎉") diff --git a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookProgress/DailyProgressView.swift b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookProgress/DailyProgressView.swift index 4e18922..bfaf9e6 100644 --- a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookProgress/DailyProgressView.swift +++ b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookProgress/DailyProgressView.swift @@ -17,8 +17,10 @@ struct DailyProgressView: View { @Query(filter: #Predicate { $0.isCompleted == false }) private var currentlyReadingBooks: [UserBook] // 현재 읽고 있는 책을 가져오는 쿼리 - let alertText = "전체쪽수를 초과해서 작성했어요!" - let alertMessage = "끝까지 읽은 게 맞나요?" + private let alertText = "전체쪽수를 초과해서 작성했어요!" + private let alertMessage = "끝까지 읽은 게 맞나요?" + + private let notificationManager = NotificationManager() private var today: Date { // TODO: today가 전날로 나와서 일단 하루 더함 @@ -78,11 +80,18 @@ struct DailyProgressView: View { book.targetEndDate = book.targetEndDate.addDays(1) readingScheduleCalculator.updateReadingProgress(for: userBook, pagesRead: pagesToReadToday, from: today) + + // 노티 세팅하기 + setNotification(userBook) + navigationCoordinator.popToRoot() } else { // 오늘 할당량 기록 readingScheduleCalculator.updateReadingProgress(for: userBook, pagesRead: pagesToReadToday, from: today) + // 노티 세팅하기 + setNotification(userBook) + if pagesToReadToday != book.totalPages { navigationCoordinator.popToRoot() } else { @@ -135,4 +144,13 @@ struct DailyProgressView: View { isTextTextFieldFocused = true } } + + private func setNotification(_ readingBook: UserBook) { + notificationManager.clearRequests() + Task { + await self.notificationManager.setupNotifications(notificationType: .morning(readingBook: readingBook)) + + await self.notificationManager.setupNotifications(notificationType: .night(readingBook: readingBook)) + } + } } diff --git a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookSetting/FinishGoalView.swift b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookSetting/FinishGoalView.swift index 7be68fb..90b1d01 100644 --- a/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookSetting/FinishGoalView.swift +++ b/FiveGuyes/FiveGuyes/Sources/Views/Screen/BookSetting/FinishGoalView.swift @@ -15,7 +15,8 @@ struct FinishGoalView: View { @State private var pagesPerDay: Int = 0 @State var userBook: UserBook? - let calculator = ReadingScheduleCalculator() + private let calculator = ReadingScheduleCalculator() + private let notificationManager = NotificationManager() var body: some View { @@ -136,6 +137,9 @@ struct FinishGoalView: View { // 책 정보 저장하기 if let userBook = userBook { modelContext.insert(userBook) // SwiftData에 새로운 책 저장 + + // 노티 세팅하기 + setNotification(userBook) navigationCoordinator.popToRoot() } else { print("책 정보 없음") @@ -163,7 +167,7 @@ struct FinishGoalView: View { let bookData = UserBook(book: BookDetails(title: book.title, author: book.author, coverURL: book.cover, totalPages: totalPages, startDate: startDate, targetEndDate: endDate, nonReadingDays: bookSettingInputModel.nonReadingDays)) calculator.calculateInitialDailyTargets(for: bookData) userBook = bookData - pagesPerDay = calculator.firstCalculatePagesPerDay(for: bookData) + pagesPerDay = calculator.firstCalculatePagesPerDay(for: bookData).pagesPerDay } } @@ -176,6 +180,14 @@ struct FinishGoalView: View { return dateFormatter.string(from: date) } + private func setNotification(_ readingBook: UserBook) { + notificationManager.clearRequests() + Task { + await self.notificationManager.setupNotifications(notificationType: .morning(readingBook: readingBook)) + + await self.notificationManager.setupNotifications(notificationType: .night(readingBook: readingBook)) + } + } } struct TextView: View { diff --git a/FiveGuyes/FiveGuyes/Sources/Views/Screen/Testing/ReadingTestView.swift b/FiveGuyes/FiveGuyes/Sources/Views/Screen/Testing/ReadingTestView.swift deleted file mode 100644 index 10ed429..0000000 --- a/FiveGuyes/FiveGuyes/Sources/Views/Screen/Testing/ReadingTestView.swift +++ /dev/null @@ -1,184 +0,0 @@ -//// -//// ReadingTestView.swift -//// FiveGuyes -//// -//// Created by zaehorang on 11/7/24. -//// -// -//import SwiftUI -// -////MARK: - 페이지 계산 모델 테스팅을 위한 Text UI입니다. -//struct ReadingTestView: View { -// @State private var readingScheduleCalculator: ReadingScheduleCalculator? = nil -// -// var body: some View { -// ScrollView { -// VStack { -// if let readingProgress = readingScheduleCalculator { -// -// DailyReadingScheduleView(readingScheduleCalculator: readingProgress) -// -// } else { -// // BookInputView로 읽기 목표 설정 화면 -// BookInputView { bookInfo in -// readingScheduleCalculator = ReadingScheduleCalculator(bookInfo: bookInfo) -// } -// .navigationTitle("책 정보 입력") -// } -// } -// } -// } -//} -// -//struct BookInputView: View { -// @State private var title = "" -// @State private var author = "" -// @State private var totalPages: String = "" -// @State private var startDate = Date() -// @State private var endDate = Calendar.current.date(byAdding: .month, value: 1, to: Date()) ?? Date() -// @State private var selectedDate = Date() -// -// @State private var nonReadingDays: [Date] = [] -// -// var onBookInfoSubmit: (BookDetails) -> Void -// -// var body: some View { -// VStack(spacing: 20) { -// TextField("총 페이지 수", text: $totalPages) -// .textFieldStyle(RoundedBorderTextFieldStyle()) -// .keyboardType(.numberPad) -// .padding() -// -// VStack(spacing: 20) { -// Text("제외할 날짜 추가") -// .font(.largeTitle) -// .padding() -// -// // 제외할 날짜 선택 -// DatePicker("제외할 날짜 선택", selection: $selectedDate, displayedComponents: .date) -// .datePickerStyle(GraphicalDatePickerStyle()) -// .padding() -// -// Button { -// // TODO: 여기는 string key로 저장해야 하려나 -// if !nonReadingDays.contains(selectedDate) { -// nonReadingDays.append(selectedDate) -// print("추가: \(selectedDate)") -// } -// } label: { -// Text("제외 날짜 추가") -// .padding() -// .frame(maxWidth: .infinity) -// .background(Color.blue) -// .foregroundColor(.white) -// .cornerRadius(8) -// } -// -// // 제외된 날짜 리스트 표시 -// VStack { -// ForEach(nonReadingDays, id: \.self) { date in -// Text(date.formattedDateString()) -// } -// } -// -// Spacer() -// } -// .padding() -// -// DatePicker("읽기 시작 날짜", selection: $startDate, displayedComponents: .date) -// .padding() -// -// DatePicker("완독 목표 날짜", selection: $endDate, displayedComponents: .date) -// .padding() -// -// Button("읽기 목표 설정") { -// if let total = Int(totalPages) { -// let bookInfo = BookDetails( -// title: title, -// author: author, -// totalPages: total, -// startDate: startDate, -// targetEndDate: endDate, -// nonReadingDays: [] -// ) -// onBookInfoSubmit(bookInfo) -// } -// } -// .padding() -// .background(Color.blue) -// .foregroundColor(.white) -// .cornerRadius(8) -// -// Spacer() -// } -// .padding() -// } -//} -// -//struct DailyReadingScheduleView: View { -// @ObservedObject var readingScheduleCalculator: ReadingScheduleCalculator -// @State private var selectedDate = Date() -// @State private var pagesRead: String = "" -// -// @State private var pagesReadInput: String = "" -// let today = Calendar.current.startOfDay(for: Date()) -// -// var body: some View { -// VStack(spacing: 20) { -// Text("일일 독서 목표") -// .font(.largeTitle) -// .padding() -// -// DatePicker("날짜 선택", selection: $selectedDate, displayedComponents: .date) -// .padding() -// -// VStack { -// ForEach(readingScheduleCalculator.dailyTargets.keys.sorted(), id: \.self) { date in -// HStack { -// Text("\(date)") -// Spacer() -// if let record = readingScheduleCalculator.dailyTargets[date] { -// Text("목표: \(record.targetPages) 페이지") -// Text("읽음: \(record.pagesRead) 페이지") -// } -// } -// -// } -// } -// -// Divider() -// -// TextField("오늘 읽은 페이지 입력", text: $pagesReadInput) -// .textFieldStyle(RoundedBorderTextFieldStyle()) -// .keyboardType(.numberPad) -// .padding() -// -// Button { -// if let pagesRead = Int(pagesReadInput) { -// readingScheduleCalculator.updateReadingProgress(for: today, pagesRead: pagesRead) -// } -// } label: { -// Text("진행 업데이트") -// .padding() -// .frame(maxWidth: .infinity) -// .background(Color.blue) -// .foregroundColor(.white) -// .cornerRadius(8) -// } -// Spacer() -// } -// .padding() -// } -//} -// -//extension Date { -// func formattedDateString() -> String { -// let formatter = DateFormatter() -// formatter.dateFormat = "yyyy-MM-dd" -// return formatter.string(from: self) -// } -//} -// -//#Preview { -// ReadingTestView() -//} From 883b612403c4e1dd8f99a38ab33c3a566d5cd9f3 Mon Sep 17 00:00:00 2001 From: zaehorang Date: Tue, 12 Nov 2024 23:07:39 +0900 Subject: [PATCH 4/4] fix: #54 Add today circle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 페이지 할당량이 없을 때도 오늘을 표시해주기 위해 초록색 원을 추가합니다. --- .../Sources/Views/Components/WeeklyPageCalendarView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FiveGuyes/FiveGuyes/Sources/Views/Components/WeeklyPageCalendarView.swift b/FiveGuyes/FiveGuyes/Sources/Views/Components/WeeklyPageCalendarView.swift index b21fa5a..8d7f302 100644 --- a/FiveGuyes/FiveGuyes/Sources/Views/Components/WeeklyPageCalendarView.swift +++ b/FiveGuyes/FiveGuyes/Sources/Views/Components/WeeklyPageCalendarView.swift @@ -99,6 +99,11 @@ struct WeeklyPageCalendarView: View { .frame(height: 44) } } else { + if index == todayIndex { // today + Circle() + .fill(Color(red: 0.07, green: 0.87, blue: 0.54)) + .frame(height: 44) + } Text("") .frame(height: 44) .foregroundColor(Color(red: 0.44, green: 0.44, blue: 0.44))