diff --git a/Core/Core/View/Base/CalendarManagerProtocol.swift b/Core/Core/View/Base/CalendarManagerProtocol.swift index fead58f02..ef7fdcb3e 100644 --- a/Core/Core/View/Base/CalendarManagerProtocol.swift +++ b/Core/Core/View/Base/CalendarManagerProtocol.swift @@ -16,8 +16,10 @@ public protocol CalendarManagerProtocol { func requestAccess() async -> Bool func courseStatus(courseID: String) -> SyncStatus func clearAllData(removeCalendar: Bool) + func isDatesChanged(courseID: String, checksum: String) -> Bool } +#if DEBUG public struct CalendarManagerMock: CalendarManagerProtocol { public func createCalendarIfNeeded() {} public func filterCoursesBySelected(fetchedCourses: [CourseForSync]) async -> [CourseForSync] {[]} @@ -27,6 +29,8 @@ public struct CalendarManagerMock: CalendarManagerProtocol { public func requestAccess() async -> Bool { true } public func courseStatus(courseID: String) -> SyncStatus { .synced } public func clearAllData(removeCalendar: Bool) {} + public func isDatesChanged(courseID: String, checksum: String) -> Bool {false} public init() {} } +#endif diff --git a/Course/Course/Presentation/Container/CourseContainerView.swift b/Course/Course/Presentation/Container/CourseContainerView.swift index 6734d05bb..64f6fd88c 100644 --- a/Course/Course/Presentation/Container/CourseContainerView.swift +++ b/Course/Course/Presentation/Container/CourseContainerView.swift @@ -200,7 +200,7 @@ public struct CourseContainerView: View { selection: $viewModel.selection, coordinate: $coordinate, collapsed: $collapsed, - dateTabIndex: 1//CourseTab.dates.rawValue + dateTabIndex: CourseTab.dates.rawValue ) .tabItem { tab.image diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 732654679..f6a970797 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -99,14 +99,6 @@ class AppAssembly: Assembly { connectivity: r.resolve(ConnectivityProtocol.self)!) }).inObjectScope(.container) - container.register(CalendarManager.self) { r in - CalendarManager( - persistence: r.resolve(ProfilePersistenceProtocol.self)!, - interactor: r.resolve(ProfileInteractorProtocol.self)!, - profileStorage: r.resolve(ProfileStorage.self)! - ) - }.inObjectScope(.container) - container.register(AuthorizationRouter.self) { r in r.resolve(Router.self)! }.inObjectScope(.container) @@ -193,7 +185,7 @@ class AppAssembly: Assembly { profileStorage: r.resolve(ProfileStorage.self)! ) } - .inObjectScope(.weak) + .inObjectScope(.container) container.register(DeepLinkManager.self) { r in DeepLinkManager( diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index ad6ce6713..f76ef7808 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -41,7 +41,7 @@ class ScreenAssembly: Assembly { config: r.resolve(ConfigProtocol.self)!, profileInteractor: r.resolve(ProfileInteractorProtocol.self)!, appStorage: r.resolve(AppStorage.self)!, - calendarManager: r.resolve(CalendarManager.self)!, + calendarManager: r.resolve(CalendarManagerProtocol.self)!, sourceScreen: sourceScreen ) } @@ -246,7 +246,7 @@ class ScreenAssembly: Assembly { interactor: r.resolve(ProfileInteractorProtocol.self)!, profileStorage: r.resolve(ProfileStorage.self)!, persistence: r.resolve(ProfilePersistenceProtocol.self)!, - calendarManager: r.resolve(CalendarManager.self)!, + calendarManager: r.resolve(CalendarManagerProtocol.self)!, connectivity: r.resolve(ConnectivityProtocol.self)! ) } diff --git a/OpenEdX/Data/ProfilePersistence.swift b/OpenEdX/Data/ProfilePersistence.swift index d28cb4cca..9b37befc9 100644 --- a/OpenEdX/Data/ProfilePersistence.swift +++ b/OpenEdX/Data/ProfilePersistence.swift @@ -86,7 +86,6 @@ public class ProfilePersistence: ProfilePersistenceProtocol { } public func deleteAllCourseStatesAndEvents() { - let fetchRequestCalendarStates: NSFetchRequest = CDCourseCalendarState.fetchRequest() let deleteRequestCalendarStates = NSBatchDeleteRequest(fetchRequest: fetchRequestCalendarStates) let fetchRequestCalendarEvents: NSFetchRequest = CDCourseCalendarEvent.fetchRequest() diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index df8d8d70a..72fa66b56 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -184,7 +184,6 @@ struct MainScreenView: View { await viewModel.prefetchDataForOffline() await viewModel.loadCalendar() } - viewModel.addShiftCourseDatesObserver() } .accentColor(Theme.Colors.accentXColor) } diff --git a/OpenEdX/View/MainScreenViewModel.swift b/OpenEdX/View/MainScreenViewModel.swift index 59ce1ccd0..740d0fd93 100644 --- a/OpenEdX/View/MainScreenViewModel.swift +++ b/OpenEdX/View/MainScreenViewModel.swift @@ -25,7 +25,7 @@ final class MainScreenViewModel: ObservableObject { private let profileInteractor: ProfileInteractorProtocol var sourceScreen: LogistrationSourceScreen private var appStorage: CoreStorage & ProfileStorage - private let calendarManager: CalendarManager + private let calendarManager: CalendarManagerProtocol private var cancellables = Set() @Published var selection: MainTab = .dashboard @@ -34,7 +34,7 @@ final class MainScreenViewModel: ObservableObject { config: ConfigProtocol, profileInteractor: ProfileInteractorProtocol, appStorage: CoreStorage & ProfileStorage, - calendarManager: CalendarManager, + calendarManager: CalendarManagerProtocol, sourceScreen: LogistrationSourceScreen = .default ) { self.analytics = analytics @@ -43,6 +43,15 @@ final class MainScreenViewModel: ObservableObject { self.appStorage = appStorage self.calendarManager = calendarManager self.sourceScreen = sourceScreen + + NotificationCenter.default.publisher(for: .shiftCourseDates, object: nil) + .sink { notification in + guard let (courseID, courseName) = notification.object as? (String, String) else { return } + Task { + await self.updateCourseDates(courseID: courseID, courseName: courseName) + } + } + .store(in: &cancellables) } public func select(tab: MainTab) { @@ -74,17 +83,6 @@ final class MainScreenViewModel: ObservableObject { await updateCalendarIfNeeded(for: username) } } - - func addShiftCourseDatesObserver() { - NotificationCenter.default.publisher(for: .shiftCourseDates, object: nil) - .sink { notification in - guard let (courseID, courseName) = notification.object as? (String, String) else { return } - Task { - await self.updateCourseDates(courseID: courseID, courseName: courseName) - } - } - .store(in: &cancellables) - } } extension MainScreenViewModel { diff --git a/Profile/Profile/Data/Network/ProfileEndpoint.swift b/Profile/Profile/Data/Network/ProfileEndpoint.swift index a30ec4c2a..a72264ebd 100644 --- a/Profile/Profile/Data/Network/ProfileEndpoint.swift +++ b/Profile/Profile/Data/Network/ProfileEndpoint.swift @@ -21,7 +21,7 @@ enum ProfileEndpoint: EndPointType { var path: String { switch self { - case .getUserProfile(let username): + case let .getUserProfile(username): return "/api/user/v1/accounts/\(username)" case .logOut: return "/oauth2/revoke_token/" @@ -29,13 +29,13 @@ enum ProfileEndpoint: EndPointType { return "/api/user/v1/accounts/\(username)" case let .uploadProfilePicture(username, _): return "/api/user/v1/accounts/\(username)/image" - case .deleteProfilePicture(username: let username): + case let .deleteProfilePicture(username): return "/api/user/v1/accounts/\(username)/image" case .deleteAccount: return "/api/user/v1/accounts/deactivate_logout/" case let .enrollmentsStatus(username): return "/api/mobile/v1/users/\(username)/enrollments_status/" - case .getCourseDates(let courseID): + case let .getCourseDates(courseID): return "/api/course_home/v1/dates/\(courseID)" } } @@ -88,12 +88,12 @@ enum ProfileEndpoint: EndPointType { "username": username ] return .requestParameters(parameters: params, encoding: JSONEncoding.default) - case .deleteAccount(password: let password): + case let .deleteAccount(password): let params: [String: String] = [ "password": password ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case .enrollmentsStatus(username: let username): + case let .enrollmentsStatus(username): return .requestParameters(parameters: nil, encoding: JSONEncoding.default) case .getCourseDates: return .requestParameters(encoding: JSONEncoding.default) diff --git a/Profile/Profile/Data/Persistence/ProfilePersistenceProtocol.swift b/Profile/Profile/Data/Persistence/ProfilePersistenceProtocol.swift index bfbdebd84..3e26799bd 100644 --- a/Profile/Profile/Data/Persistence/ProfilePersistenceProtocol.swift +++ b/Profile/Profile/Data/Persistence/ProfilePersistenceProtocol.swift @@ -20,6 +20,7 @@ public protocol ProfilePersistenceProtocol { func getCourseCalendarEvents(for courseId: String) -> [CourseCalendarEvent] } +#if DEBUG public struct ProfilePersistenceMock: ProfilePersistenceProtocol { public func getCourseState(courseID: String) -> CourseCalendarState? { nil } public func getAllCourseStates() -> [CourseCalendarState] {[]} @@ -31,6 +32,7 @@ public struct ProfilePersistenceMock: ProfilePersistenceProtocol { public func removeAllCourseCalendarEvents() {} public func getCourseCalendarEvents(for courseId: String) -> [CourseCalendarEvent] { [] } } +#endif public final class ProfileBundle { private init() {} diff --git a/Profile/Profile/Data/ProfileRepository.swift b/Profile/Profile/Data/ProfileRepository.swift index 8679aaad4..2bd35cdfe 100644 --- a/Profile/Profile/Data/ProfileRepository.swift +++ b/Profile/Profile/Data/ProfileRepository.swift @@ -266,7 +266,7 @@ class ProfileRepositoryMock: ProfileRepositoryProtocol { DataLayer.EnrollmentsStatusElement(courseID: "7", courseName: "Course 7", isActive: true), DataLayer.EnrollmentsStatusElement(courseID: "8", courseName: "Course 8", isActive: true), DataLayer.EnrollmentsStatusElement(courseID: "9", courseName: "Course 9", isActive: true), - ] + ] return result.domain } diff --git a/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift b/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift index da6837b49..157a6aaaf 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift @@ -62,7 +62,7 @@ public class CalendarManager: CalendarManagerProtocol { case ProfileLocalization.Calendar.Dropdown.icloud: return iCloud ?? local ?? fallback case ProfileLocalization.Calendar.Dropdown.local: - return fallback ?? local + return fallback ?? local default: return iCloud ?? local ?? fallback } @@ -176,7 +176,6 @@ public class CalendarManager: CalendarManagerProtocol { let events = generateEvents(for: dateBlocks, courseName: courseName, calendar: calendar) var saveSuccessful = true events.forEach { event in - // if !alreadyExist(event: event) { do { try eventStore.save(event, span: .thisEvent) persistence.saveCourseCalendarEvent( @@ -245,7 +244,7 @@ public class CalendarManager: CalendarManagerProtocol { private func calendarEvent(for block: CourseDateBlock, courseName: String, calendar: EKCalendar) -> EKEvent? { guard !block.title.isEmpty else { return nil } - let title = block.title// + ": " + courseName//calendar.title + let title = block.title let startDate = block.date.addingTimeInterval(Double(alertOffset) * 3600) let secondAlert = startDate.addingTimeInterval(Double(alertOffset) * 86400) let endDate = block.date @@ -269,7 +268,7 @@ public class CalendarManager: CalendarManagerProtocol { private func calendarEvent(for blocks: [CourseDateBlock], courseName: String, calendar: EKCalendar) -> EKEvent? { guard let block = blocks.first, !block.title.isEmpty else { return nil } - let title = block.title// + ": " + courseName//calendar.title + let title = block.title let startDate = block.date.addingTimeInterval(Double(alertOffset) * 3600) let secondAlert = startDate.addingTimeInterval(Double(alertOffset) * 86400) let endDate = block.date @@ -336,13 +335,15 @@ public class CalendarManager: CalendarManagerProtocol { return shortUrl } - private func generateEvent(title: String, - startDate: Date, - endDate: Date, - secondAlert: Date, - notes: String, - location: String, - calendar: EKCalendar) -> EKEvent { + private func generateEvent( + title: String, + startDate: Date, + endDate: Date, + secondAlert: Date, + notes: String, + location: String, + calendar: EKCalendar + ) -> EKEvent { let event = EKEvent(eventStore: eventStore) event.title = title event.location = location diff --git a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift index 47b2e019f..1f1aae56b 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift @@ -38,10 +38,10 @@ public class DatesAndCalendarViewModel: ObservableObject { @Published var coursesForSync = [CourseForSync]() - var coursesForSyncBeforeChanges = [CourseForSync]() + private var coursesForSyncBeforeChanges = [CourseForSync]() - var coursesForDeleting = [CourseForSync]() - var coursesForAdding = [CourseForSync]() + private var coursesForDeleting = [CourseForSync]() + private var coursesForAdding = [CourseForSync]() @Published var synced: Bool = true @Published var hideInactiveCourses: Bool = false @@ -56,7 +56,7 @@ public class DatesAndCalendarViewModel: ObservableObject { } } - let accounts: [DropDownPicker.DownPickerOption] = [ + private let accounts: [DropDownPicker.DownPickerOption] = [ .init(title: ProfileLocalization.Calendar.Dropdown.icloud), .init(title: ProfileLocalization.Calendar.Dropdown.local) ] @@ -291,7 +291,7 @@ public class DatesAndCalendarViewModel: ObservableObject { return syncedCourses } - func deleteOldCalendarIfNeeded() { + func deleteOldCalendarIfNeeded() async { guard let calSettings = profileStorage.calendarSettings else { return } let courseCalendarStates = persistence.getAllCourseStates() let courseCountChanges = courseCalendarStates.count != coursesForSync.count @@ -304,9 +304,7 @@ public class DatesAndCalendarViewModel: ObservableObject { calendarManager.removeOldCalendar() saveCalendarOptions() persistence.removeAllCourseCalendarEvents() - Task { - await fetchCourses() - } + await fetchCourses() } private func syncSelectedCourse( @@ -344,7 +342,6 @@ public class DatesAndCalendarViewModel: ObservableObject { await calendarManager.removeOutdatedEvents(courseID: course.courseID) persistence.removeCourseState(courseID: course.courseID) persistence.removeCourseCalendarEvents(for: course.courseID) - // Обновляем статус синхронизации курса if let index = self.coursesForSync.firstIndex(where: { $0.courseID == course.courseID }) { self.coursesForSync[index].synced = false } @@ -387,7 +384,6 @@ public class DatesAndCalendarViewModel: ObservableObject { } } } else { - // Убираем из массивов, если состояние курса совпадает с начальным if let index = coursesForAdding.firstIndex(where: { $0.courseID == course.courseID }) { coursesForAdding.remove(at: index) } diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift index dc3190b04..73fe0a39a 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift @@ -67,30 +67,30 @@ public enum DropDownColor: String { } } -public struct DropDownPicker: View { +struct DropDownPicker: View { - public struct DownPickerOption: Hashable { + struct DownPickerOption: Hashable { let title: String let color: Color? let colorString: String? - public init(title: String) { + init(title: String) { self.title = title self.color = nil self.colorString = nil } - public init(color: DropDownColor) { + init(color: DropDownColor) { self.title = color.title self.color = color.color self.colorString = color.rawValue } - public func hash(into hasher: inout Hasher) { + func hash(into hasher: inout Hasher) { hasher.combine(title) } - public static func == (lhs: DownPickerOption, rhs: DownPickerOption) -> Bool { + static func == (lhs: DownPickerOption, rhs: DownPickerOption) -> Bool { lhs.title == rhs.title } } @@ -104,13 +104,13 @@ public struct DropDownPicker: View { @State private var index = 1000.0 @State var zindex = 1000.0 - public init(selection: Binding, state: DropDownPickerState, options: [DownPickerOption]) { + init(selection: Binding, state: DropDownPickerState, options: [DownPickerOption]) { self._selection = selection self.state = state self.options = options } - public var body: some View { + var body: some View { GeometryReader { let size = $0.size VStack(spacing: 0) { diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Models/CalendarSettings.swift b/Profile/Profile/Presentation/DatesAndCalendar/Models/CalendarSettings.swift index c50843551..7c1970d0c 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/Models/CalendarSettings.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/Models/CalendarSettings.swift @@ -5,7 +5,7 @@ // Created by  Stepanok Ivan on 03.06.2024. // -import UIKit +import Foundation public struct CalendarSettings: Codable { public var colorSelection: String diff --git a/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift index f8ea492de..432303fa6 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift @@ -140,7 +140,7 @@ public struct SyncCalendarOptionsView: View { } Task { - viewModel.deleteOldCalendarIfNeeded() + await viewModel.deleteOldCalendarIfNeeded() } }, onCloseTapped: {