-
Notifications
You must be signed in to change notification settings - Fork 1
아이폰 캘린더 연동 & 토덕 서버의 일정과 통합하여 함께 표시 #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f96519b
4d59a08
89d2f95
bf8bc09
1df4ee5
16e3296
f22ceda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,75 @@ | ||
| import TDCore | ||
| import EventKit | ||
| import TDDomain | ||
| import Foundation | ||
|
|
||
| public final class ScheduleRepositoryImpl: ScheduleRepository { | ||
| private let service: ScheduleService | ||
| private let storage: ScheduleStorage | ||
|
|
||
| public init(service: ScheduleService) { | ||
| public init( | ||
| service: ScheduleService, | ||
| storage: ScheduleStorage | ||
| ) { | ||
| self.service = service | ||
| self.storage = storage | ||
| } | ||
|
|
||
| public func createSchedule(schedule: Schedule) async throws { | ||
| let scheduleRequestDTO = ScheduleRequestDTO(schedule: schedule) | ||
| try await service.createSchedule(schedule: scheduleRequestDTO) | ||
| } | ||
|
|
||
| public func fetchScheduleList(startDate: String, endDate: String) async throws -> [Schedule] { | ||
| public func fetchServerScheduleList(startDate: String, endDate: String) async throws -> [Schedule] { | ||
| let responseDTO = try await service.fetchScheduleList(startDate: startDate, endDate: endDate) | ||
| return responseDTO.scheduleHeadDtos.map { $0.convertToSchedule() } | ||
| } | ||
|
|
||
| public func fetchSchedule() async throws -> Schedule { | ||
| return | ||
| Schedule( | ||
| id: 0, | ||
| title: "title", | ||
| category: TDCategory(colorHex: "", imageName: ""), | ||
| startDate: "", | ||
| endDate: "", | ||
| isAllDay: false, | ||
| time: nil, | ||
| public func fetchLocalCalendarScheduleList(startDate: String, endDate: String) async throws -> [Schedule] { | ||
| let format = DateFormatType.yearMonthDay | ||
| let calendar = Calendar.current | ||
|
|
||
| guard let startDay = Date.convertFromString(startDate, format: format), | ||
| let endDay = Date.convertFromString(endDate, format: format) else { | ||
| throw TDDataError.convertDTOFailure | ||
| } | ||
|
|
||
| let rangeStartDate = calendar.startOfDay(for: startDay) | ||
|
|
||
| guard let rangeEndDate = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: endDay)) else { | ||
| throw TDDataError.convertDTOFailure | ||
| } | ||
|
|
||
| let events = try await storage.fetchEvents(from: rangeStartDate, to: rangeEndDate) | ||
|
|
||
| return mapToSchedules(from: events) | ||
| } | ||
|
|
||
| private func mapToSchedules(from events: [EKEvent]) -> [Schedule] { | ||
| return events.map { event in | ||
| return Schedule( | ||
| id: event.eventIdentifier.hashValue, | ||
| title: event.title ?? "", | ||
| category: TDCategory(colorHex: "#FFFFFF", imageName: "none"), | ||
| startDate: event.startDate.convertToString(formatType: .yearMonthDay), | ||
| endDate: event.endDate.convertToString(formatType: .yearMonthDay), | ||
| isAllDay: event.isAllDay, | ||
| time: event.isAllDay ? nil : event.startDate.convertToString(formatType: .time24Hour), | ||
| repeatDays: nil, | ||
| alarmTime: nil, | ||
| place: nil, | ||
| memo: nil, | ||
| place: event.location, | ||
| memo: event.notes, | ||
| isFinished: false, | ||
| scheduleRecords: nil | ||
| scheduleRecords: nil, | ||
| source: .localCalendar | ||
| ) | ||
| } | ||
|
Comment on lines
+48
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion hashValue 기반 로컬 이벤트 ID는 비결정적입니다 — 안정적/충돌-회피 ID로 교체 필요
제안 diff(해당 라인 교체): - id: event.eventIdentifier.hashValue,
+ id: makeLocalCalendarId(eventIdentifier: event.eventIdentifier),지도 함수(파일 내 private 영역에 추가): // CryptoKit 필요: import CryptoKit
private func makeLocalCalendarId(eventIdentifier: String) -> Int {
// 결정적 양의 해시값 생성
let digest = SHA256.hash(data: Data(eventIdentifier.utf8))
let v = digest.prefix(8).reduce(UInt64(0)) { ($0 << 8) | UInt64($1) } // 상위 8바이트 -> 64비트
let positive = Int(truncatingIfNeeded: v & 0x7fffffffffffffff)
// 서버(Int 양수로 가정)와 충돌 회피: 음수 영역으로 강제
return positive > 0 ? -positive : positive
}추가적으로(선택):
필요하시면 위 변경에 대한 유닛 테스트(같은 |
||
| } | ||
|
|
||
| public func finishSchedule(scheduleId: Int, isComplete: Bool, queryDate: String) async throws { | ||
| try await service.finishSchedule(scheduleId: scheduleId, isComplete: isComplete, queryDate: queryDate) | ||
| } | ||
|
|
||
|
|
||
| public func updateSchedule(scheduleId: Int, isOneDayDeleted: Bool, queryDate: String, scheduleData: Schedule) async throws { | ||
| let scheduleData = ScheduleDataDTO(schedule: scheduleData) | ||
| let scheduleUpdateRequestDTO = ScheduleUpdateRequestDTO( | ||
|
|
@@ -61,8 +88,4 @@ public final class ScheduleRepositoryImpl: ScheduleRepository { | |
| queryDate: queryDate | ||
| ) | ||
| } | ||
|
|
||
| public func moveTomorrowSchedule(scheduleId: Int) async throws { | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import Foundation | ||
| import EventKit | ||
|
|
||
|
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain플랫폼/권한 변화 대응 확인 요청 iOS 17 이후 EventKit 권한 상태 값이 확장되었습니다. 구현부( 아래 스캔으로 권한 분기 로직들을 점검해 주세요: 🏁 Script executed: #!/bin/bash
# EventKit 권한 체크 사용처 점검
rg -nP --type=swift -C2 'EKEventStore\.authorizationStatus\('
rg -nP --type=swift -C2 '\.requestAccess\(\s*to:\s*\.event'Length of output: 1908 iOS 17용 EventKit 권한 분기 로직 보강 필요 iOS 17부터 다음 항목을 반영해 주세요: • ScheduleStorageImpl.swift (fetchEvents) func fetchEvents(from startDate: Date, to endDate: Date) async throws -> [EKEvent] {
- guard EKEventStore.authorizationStatus(for: .event) == .authorized else {
- throw TDDataError.permissionDenied
- }
+ let status = EKEventStore.authorizationStatus(for: .event)
+ switch status {
+ case .authorized, .fullAccess:
+ break
+ default:
+ throw TDDataError.permissionDenied
+ }
// …이하 기존 로직
}• MainTabBarCoordinator.swift (권한 요청) if #available(iOS 17, *) {
eventStore.requestFullAccessToEvents { granted, error in
// granted==true → full access 승인
}
} else {
eventStore.requestAccess(to: .event) { granted, error in
// 기존 처리
}
}• Info.plist 위 변경 사항을 적용해 iOS 17 권한 상태(.fullAccess, .writeOnly 등)와 요청 API 모두 정상 동작하도록 확인 부탁드립니다. |
||
| /// 캘린더 데이터 소스(Storage)가 수행해야 하는 기능을 정의하는 프로토콜입니다. | ||
| /// EventKit 프레임워크에 직접 접근하여 원시 데이터인 `EKEvent`를 가져오는 역할을 합니다. | ||
| public protocol ScheduleStorage { | ||
|
|
||
| /// 지정된 기간에 해당하는 모든 캘린더 이벤트를 가져옵니다. | ||
| /// - Parameters: | ||
| /// - startDate: 조회를 시작할 날짜 | ||
| /// - endDate: 조회를 종료할 날짜 | ||
| /// - Returns: EventKit의 원시 데이터 모델인 `EKEvent`의 배열 | ||
| /// - Throws: 권한이 없거나 데이터를 가져오는 중 오류가 발생하면 에러를 던집니다. | ||
| func fetchEvents(from startDate: Date, to endDate: Date) async throws -> [EKEvent] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import Foundation | ||
| import TDCore | ||
|
|
||
| public protocol FetchAllSchedulesUseCase { | ||
| func execute(startDate: String, endDate: String) async throws -> [Date: [Schedule]] | ||
| } | ||
|
|
||
| public final class FetchAllSchedulesUseCaseImpl: FetchAllSchedulesUseCase { | ||
| private let serverUseCase: FetchServerScheduleListUseCase | ||
| private let localUseCase: FetchLocalCalendarScheduleListUseCase | ||
|
|
||
| public init( | ||
| serverUseCase: FetchServerScheduleListUseCase, | ||
| localUseCase: FetchLocalCalendarScheduleListUseCase | ||
| ) { | ||
| self.serverUseCase = serverUseCase | ||
| self.localUseCase = localUseCase | ||
| } | ||
|
|
||
| public func execute(startDate: String, endDate: String) async throws -> [Date: [Schedule]] { | ||
| async let serverSchedulesTask = serverUseCase.execute(startDate: startDate, endDate: endDate) | ||
| async let localSchedulesTask = localUseCase.execute(startDate: startDate, endDate: endDate) | ||
|
|
||
| let (serverSchedules, localSchedules) = try await (serverSchedulesTask, localSchedulesTask) | ||
|
|
||
| var combinedSchedules = serverSchedules | ||
| for (date, schedules) in localSchedules { | ||
| combinedSchedules[date, default: []].append(contentsOf: schedules) | ||
| } | ||
|
|
||
| return combinedSchedules | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import Foundation | ||
|
|
||
| public protocol FetchLocalCalendarScheduleListUseCase { | ||
| func execute(startDate: String, endDate: String) async throws -> [Date: [Schedule]] | ||
| } | ||
|
|
||
| public final class FetchLocalCalendarScheduleListUseCaseImpl: FetchLocalCalendarScheduleListUseCase { | ||
| private let repository: ScheduleRepository | ||
|
|
||
| public init(repository: ScheduleRepository) { | ||
| self.repository = repository | ||
| } | ||
|
|
||
| public func execute( | ||
| startDate: String, | ||
| endDate: String | ||
| ) async throws -> [Date: [Schedule]] { | ||
| let schedules = try await repository.fetchLocalCalendarScheduleList( | ||
| startDate: startDate, | ||
| endDate: endDate | ||
| ) | ||
|
|
||
| return groupSchedulesByDay(schedules: schedules) | ||
| } | ||
|
|
||
| private func groupSchedulesByDay(schedules: [Schedule]) -> [Date: [Schedule]] { | ||
| var groupedDictionary = [Date: [Schedule]]() | ||
| let calendar = Calendar.current | ||
|
|
||
| for schedule in schedules { | ||
| guard | ||
| let startDate = Date.convertFromString(schedule.startDate, format: .yearMonthDay), | ||
| let endDate = Date.convertFromString(schedule.endDate, format: .yearMonthDay) | ||
| else { continue } | ||
|
|
||
| var currentDate = startDate | ||
| while calendar.startOfDay(for: currentDate) <= calendar.startOfDay(for: endDate) { | ||
| let dayKey = calendar.startOfDay(for: currentDate) | ||
| groupedDictionary[dayKey, default: []].append(schedule) | ||
|
|
||
| if let nextDay = calendar.date(byAdding: .day, value: 1, to: currentDate) { | ||
| currentDate = nextDay | ||
| } else { | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return groupedDictionary | ||
| } | ||
| } |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 hashValue 값이랑 서버에서 저장하고있는 id값이랑 겹치는 일은 없겠죠 ?