From a17038f4d9583220263b699590cbbfbf01ca8a2f Mon Sep 17 00:00:00 2001 From: IvanStepanok <128456094+IvanStepanok@users.noreply.github.com> Date: Wed, 29 May 2024 15:53:20 +0300 Subject: [PATCH] feat: [FC-0047] calendar synchronization design (#439) * feat: calendar synchronization design * fix: address feedback * fix: address feedback * fix: address feedback * fix: address feedback * fix: address feedback * fix: update profile mock file * fix: address feedback * fix: address feedback remove old errorAlertView variables --- Core/Core.xcodeproj/project.pbxproj | 12 + .../Assets.xcassets/Calendar/Contents.json | 6 + .../calendarAccess.imageset/Contents.json | 12 + .../calendarAccess.svg | 10 + .../syncFailed.imageset/Contents.json | 12 + .../syncFailed.imageset/syncFailed.svg | 3 + .../syncOffline.imageset/Contents.json | 12 + .../syncOffline.imageset/syncOffline.svg | 3 + .../Calendar/synced.imageset/Contents.json | 12 + .../Calendar/synced.imageset/synced.svg | 3 + .../calendarSyncIcon.imageset/Contents.json | 15 ++ .../calendarSyncIcon.svg | 4 + Core/Core/Extensions/Bundle.swift | 15 ++ Core/Core/Extensions/ViewExtension.swift | 16 ++ Core/Core/SwiftGen/Assets.swift | 5 + Core/Core/View/Base/CheckBoxView.swift | 13 +- Core/Core/View/Base/ErrorAlertView.swift | 35 +++ Core/Core/View/Base/NavigationTitle.swift | 52 ++++ Core/Core/View/Base/StyledButton.swift | 6 +- OpenEdX/DI/ScreenAssembly.swift | 6 + OpenEdX/Router.swift | 21 ++ Profile/Profile.xcodeproj/project.pbxproj | 56 +++++ .../DatesAndCalendar/CoursesToSyncView.swift | 134 ++++++++++ .../DatesAndCalendarView.swift | 195 +++++++++++++++ .../DatesAndCalendarViewModel.swift | 131 ++++++++++ .../Elements/AssignmentStatusView.swift | 90 +++++++ .../Elements/CalendarDialogView.swift | 187 ++++++++++++++ .../Elements/DropDownPicker.swift | 184 ++++++++++++++ .../Elements/NewCalendarView.swift | 154 ++++++++++++ .../Elements/SyncSelector.swift | 60 +++++ .../Elements/ToggleWithDescriptionView.swift | 98 ++++++++ .../SyncCalendarOptionsView.swift | 231 ++++++++++++++++++ .../Presentation/Profile/ProfileView.swift | 2 +- .../Profile/Presentation/ProfileRouter.swift | 12 + .../Presentation/Settings/SettingsView.swift | 29 +++ Profile/Profile/SwiftGen/Strings.swift | 132 ++++++++++ Profile/Profile/en.lproj/Localizable.strings | 65 +++++ Profile/Profile/uk.lproj/Localizable.strings | 63 +++++ .../ProfileTests/ProfileMock.generated.swift | 45 ++++ 39 files changed, 2136 insertions(+), 5 deletions(-) create mode 100644 Core/Core/Assets.xcassets/Calendar/Contents.json create mode 100644 Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/calendarAccess.svg create mode 100644 Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/syncFailed.svg create mode 100644 Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/syncOffline.svg create mode 100644 Core/Core/Assets.xcassets/Calendar/synced.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Calendar/synced.imageset/synced.svg create mode 100644 Core/Core/Assets.xcassets/calendarSyncIcon.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/calendarSyncIcon.imageset/calendarSyncIcon.svg create mode 100644 Core/Core/Extensions/Bundle.swift create mode 100644 Core/Core/View/Base/ErrorAlertView.swift create mode 100644 Core/Core/View/Base/NavigationTitle.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/AssignmentStatusView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/CalendarDialogView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/NewCalendarView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/SyncSelector.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/Elements/ToggleWithDescriptionView.swift create mode 100644 Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index f718ae857..25f7353d2 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 0254D1912BCD699F000CDE89 /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254D1902BCD699F000CDE89 /* RefreshProgressView.swift */; }; 0255D5582936283A004DBC1A /* UploadBodyEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0255D55729362839004DBC1A /* UploadBodyEncoding.swift */; }; 0259104A29C4A5B6004B5A55 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0259104929C4A5B6004B5A55 /* UserSettings.swift */; }; + 025A20502C071EB0003EA08D /* ErrorAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025A204F2C071EB0003EA08D /* ErrorAlertView.swift */; }; 025B36752A13B7D5001A640E /* UnitButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025B36742A13B7D5001A640E /* UnitButtonView.swift */; }; 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 025EF2F52971740000B838AB /* YouTubePlayerKit */; }; 0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0260E57F28FD792800BBBE18 /* WebUnitViewModel.swift */; }; @@ -56,6 +57,7 @@ 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027BD3BA2909478B00392132 /* UIView+EnclosingScrollView.swift */; }; 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027BD3BB2909478B00392132 /* UIResponder+CurrentResponder.swift */; }; 027BD3C52909707700392132 /* Shake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027BD3C42909707700392132 /* Shake.swift */; }; + 027F1BF72C071C820001A24C /* NavigationTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027F1BF62C071C820001A24C /* NavigationTitle.swift */; }; 0282DA7328F98CC9003C3F07 /* WebUnitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0282DA7228F98CC9003C3F07 /* WebUnitView.swift */; }; 0283347D28D4D3DE00C828FC /* Data_Discovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283347C28D4D3DE00C828FC /* Data_Discovery.swift */; }; 0283348028D4DCD200C828FC /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283347F28D4DCD200C828FC /* ViewExtension.swift */; }; @@ -64,6 +66,7 @@ 028F9F37293A44C700DE65D0 /* Data_ResetPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F9F36293A44C700DE65D0 /* Data_ResetPassword.swift */; }; 028F9F39293A452B00DE65D0 /* ResetPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F9F38293A452B00DE65D0 /* ResetPassword.swift */; }; 0295C885299B99DD00ABE571 /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0295C884299B99DD00ABE571 /* RefreshableScrollView.swift */; }; + 029EE3ED2BF6650500F64F33 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029EE3EC2BF6650500F64F33 /* Bundle.swift */; }; 02A463112AEA966C00331037 /* AppReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A463102AEA966C00331037 /* AppReviewView.swift */; }; 02A4833529B8A73400D33F33 /* CorePersistenceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A4833429B8A73400D33F33 /* CorePersistenceProtocol.swift */; }; 02A4833829B8A8F900D33F33 /* CoreDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 02A4833629B8A8F800D33F33 /* CoreDataModel.xcdatamodeld */; }; @@ -221,6 +224,7 @@ 0254D1902BCD699F000CDE89 /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = ""; }; 0255D55729362839004DBC1A /* UploadBodyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadBodyEncoding.swift; sourceTree = ""; }; 0259104929C4A5B6004B5A55 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; + 025A204F2C071EB0003EA08D /* ErrorAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertView.swift; sourceTree = ""; }; 025B36742A13B7D5001A640E /* UnitButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitButtonView.swift; sourceTree = ""; }; 0260E57F28FD792800BBBE18 /* WebUnitViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebUnitViewModel.swift; sourceTree = ""; }; 027BD3912907D88F00392132 /* Data_RegistrationFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_RegistrationFields.swift; sourceTree = ""; }; @@ -238,6 +242,7 @@ 027BD3BA2909478B00392132 /* UIView+EnclosingScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+EnclosingScrollView.swift"; sourceTree = ""; }; 027BD3BB2909478B00392132 /* UIResponder+CurrentResponder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIResponder+CurrentResponder.swift"; sourceTree = ""; }; 027BD3C42909707700392132 /* Shake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shake.swift; sourceTree = ""; }; + 027F1BF62C071C820001A24C /* NavigationTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitle.swift; sourceTree = ""; }; 0282DA7228F98CC9003C3F07 /* WebUnitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebUnitView.swift; sourceTree = ""; }; 0283347C28D4D3DE00C828FC /* Data_Discovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_Discovery.swift; sourceTree = ""; }; 0283347F28D4DCD200C828FC /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = ""; }; @@ -246,6 +251,7 @@ 028F9F36293A44C700DE65D0 /* Data_ResetPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_ResetPassword.swift; sourceTree = ""; }; 028F9F38293A452B00DE65D0 /* ResetPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPassword.swift; sourceTree = ""; }; 0295C884299B99DD00ABE571 /* RefreshableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshableScrollView.swift; sourceTree = ""; }; + 029EE3EC2BF6650500F64F33 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 02A463102AEA966C00331037 /* AppReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewView.swift; sourceTree = ""; }; 02A4833429B8A73400D33F33 /* CorePersistenceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePersistenceProtocol.swift; sourceTree = ""; }; 02A4833729B8A8F800D33F33 /* CoreDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataModel.xcdatamodel; sourceTree = ""; }; @@ -486,6 +492,7 @@ 02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */, E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */, BA981BCD2B8F5C49005707C2 /* Sequence+Extensions.swift */, + 029EE3EC2BF6650500F64F33 /* Bundle.swift */, ); path = Extensions; sourceTree = ""; @@ -711,6 +718,8 @@ 025B36742A13B7D5001A640E /* UnitButtonView.swift */, 0727877C28D25212002E9142 /* ProgressBar.swift */, 022C64E329AE0191000F532B /* TextWithUrls.swift */, + 027F1BF62C071C820001A24C /* NavigationTitle.swift */, + 025A204F2C071EB0003EA08D /* ErrorAlertView.swift */, 0727878028D25EFD002E9142 /* SnackBarView.swift */, 02F6EF3A28D9B8EC00835477 /* CourseCellView.swift */, 024FCCFF28EF1CD300232339 /* WebBrowser.swift */, @@ -1058,6 +1067,7 @@ 02F6EF4A28D9F0A700835477 /* DateExtension.swift in Sources */, DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */, 027BD3AF2909475000392132 /* DismissKeyboardTapHandler.swift in Sources */, + 027F1BF72C071C820001A24C /* NavigationTitle.swift in Sources */, 06619EAA2B8F2936001FAADE /* ReadabilityModifier.swift in Sources */, BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */, 02E225B0291D29EB0067769A /* UrlExtension.swift in Sources */, @@ -1128,6 +1138,7 @@ 023A4DD4299E66BD006C0E48 /* OfflineSnackBarView.swift in Sources */, 021D925728DCF12900ACC565 /* AlertView.swift in Sources */, 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, + 025A20502C071EB0003EA08D /* ErrorAlertView.swift in Sources */, 02E93F852AEBAEBC006C4750 /* AppReviewViewModel.swift in Sources */, A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */, 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, @@ -1192,6 +1203,7 @@ 0231CDBE2922422D00032416 /* CSSInjector.swift in Sources */, 064987982B4D69FF0071642A /* CSSInjectionProtocol.swift in Sources */, BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */, + 029EE3ED2BF6650500F64F33 /* Bundle.swift in Sources */, BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */, 0236961928F9A26900EEF206 /* AuthRepository.swift in Sources */, 023A1136291432B200D0D354 /* RegistrationTextField.swift in Sources */, diff --git a/Core/Core/Assets.xcassets/Calendar/Contents.json b/Core/Core/Assets.xcassets/Calendar/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/Contents.json b/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/Contents.json new file mode 100644 index 000000000..b78b96492 --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "calendarAccess.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/calendarAccess.svg b/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/calendarAccess.svg new file mode 100644 index 000000000..d80a2356a --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/calendarAccess.imageset/calendarAccess.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/Contents.json b/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/Contents.json new file mode 100644 index 000000000..6ff388e4c --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "syncFailed.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/syncFailed.svg b/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/syncFailed.svg new file mode 100644 index 000000000..fe6e39f14 --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/syncFailed.imageset/syncFailed.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/Contents.json b/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/Contents.json new file mode 100644 index 000000000..1a75410c4 --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "syncOffline.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/syncOffline.svg b/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/syncOffline.svg new file mode 100644 index 000000000..6c7bec7f2 --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/syncOffline.imageset/syncOffline.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/Assets.xcassets/Calendar/synced.imageset/Contents.json b/Core/Core/Assets.xcassets/Calendar/synced.imageset/Contents.json new file mode 100644 index 000000000..5d1255461 --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/synced.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "synced.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Calendar/synced.imageset/synced.svg b/Core/Core/Assets.xcassets/Calendar/synced.imageset/synced.svg new file mode 100644 index 000000000..71652aafd --- /dev/null +++ b/Core/Core/Assets.xcassets/Calendar/synced.imageset/synced.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/Contents.json b/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/Contents.json new file mode 100644 index 000000000..d75d6b0b4 --- /dev/null +++ b/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "calendarSyncIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/calendarSyncIcon.svg b/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/calendarSyncIcon.svg new file mode 100644 index 000000000..7708fa304 --- /dev/null +++ b/Core/Core/Assets.xcassets/calendarSyncIcon.imageset/calendarSyncIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Core/Core/Extensions/Bundle.swift b/Core/Core/Extensions/Bundle.swift new file mode 100644 index 000000000..b46037f19 --- /dev/null +++ b/Core/Core/Extensions/Bundle.swift @@ -0,0 +1,15 @@ +// +// Bundle.swift +// Core +// +// Created by  Stepanok Ivan on 16.05.2024. +// + +import Foundation + +public extension Bundle { + var applicationName: String? { + object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? + object(forInfoDictionaryKey: "CFBundleName") as? String + } +} diff --git a/Core/Core/Extensions/ViewExtension.swift b/Core/Core/Extensions/ViewExtension.swift index 71392ebd7..e791925ba 100644 --- a/Core/Core/Extensions/ViewExtension.swift +++ b/Core/Core/Extensions/ViewExtension.swift @@ -237,6 +237,22 @@ public extension View { .padding(.horizontal, 8) .offset(y: topPadding) } + + @ViewBuilder + private func onTapBackgroundContent(enabled: Bool, _ action: @escaping () -> Void) -> some View { + if enabled { + Color.clear + .frame(width: UIScreen.main.bounds.width * 2, height: UIScreen.main.bounds.height * 2) + .contentShape(Rectangle()) + .onTapGesture(perform: action) + } + } + + func onTapBackground(enabled: Bool, _ action: @escaping () -> Void) -> some View { + background( + onTapBackgroundContent(enabled: enabled, action) + ) + } } public extension View { diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index a6cb7b057..dfa0c9677 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -24,6 +24,10 @@ public typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable identifier_name line_length nesting type_body_length type_name public enum CoreAssets { + public static let calendarAccess = ImageAsset(name: "calendarAccess") + public static let syncFailed = ImageAsset(name: "syncFailed") + public static let syncOffline = ImageAsset(name: "syncOffline") + public static let synced = ImageAsset(name: "synced") public static let appleButtonColor = ColorAsset(name: "AppleButtonColor") public static let facebookButtonColor = ColorAsset(name: "FacebookButtonColor") public static let googleButtonColor = ColorAsset(name: "GoogleButtonColor") @@ -93,6 +97,7 @@ public enum CoreAssets { public static let alarm = ImageAsset(name: "alarm") public static let arrowLeft = ImageAsset(name: "arrowLeft") public static let arrowRight16 = ImageAsset(name: "arrowRight16") + public static let calendarSyncIcon = ImageAsset(name: "calendarSyncIcon") public static let certificate = ImageAsset(name: "certificate") public static let certificateBadge = ImageAsset(name: "certificateBadge") public static let check = ImageAsset(name: "check") diff --git a/Core/Core/View/Base/CheckBoxView.swift b/Core/Core/View/Base/CheckBoxView.swift index efed96ccc..267d21463 100644 --- a/Core/Core/View/Base/CheckBoxView.swift +++ b/Core/Core/View/Base/CheckBoxView.swift @@ -13,11 +13,18 @@ public struct CheckBoxView: View { @Binding private var checked: Bool private var text: String private var font: Font + private let color: Color - public init(checked: Binding, text: String, font: Font = Theme.Fonts.labelLarge) { + public init( + checked: Binding, + text: String, + font: Font = Theme.Fonts.labelLarge, + color: Color = Theme.Colors.textPrimary + ) { self._checked = checked self.text = text self.font = font + self.color = color } public var body: some View { @@ -26,11 +33,11 @@ public struct CheckBoxView: View { systemName: checked ? "checkmark.square.fill" : "square" ) .foregroundColor( - checked ? Theme.Colors.accentXColor : Theme.Colors.textPrimary + checked ? Theme.Colors.accentXColor : color ) Text(text) .font(font) - .foregroundColor(Theme.Colors.textPrimary) + .foregroundColor(color) } .onTapGesture { withAnimation(.linear(duration: 0.1)) { diff --git a/Core/Core/View/Base/ErrorAlertView.swift b/Core/Core/View/Base/ErrorAlertView.swift new file mode 100644 index 000000000..5256f0f7f --- /dev/null +++ b/Core/Core/View/Base/ErrorAlertView.swift @@ -0,0 +1,35 @@ +// +// ErrorAlertView.swift +// Core +// +// Created by  Stepanok Ivan on 29.05.2024. +// + +import SwiftUI +import Theme + +public struct ErrorAlertView: View { + + @Binding var errorMessage: String? + + public init(errorMessage: Binding) { + self._errorMessage = errorMessage + } + + public var body: some View { + VStack { + Spacer() + SnackBarView(message: errorMessage) + .transition(.move(edge: .bottom)) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + Theme.Timeout.snackbarMessageLongTimeout) { + errorMessage = nil + } + } + } + } +} + +#Preview { + ErrorAlertView(errorMessage: .constant("Error message")) +} diff --git a/Core/Core/View/Base/NavigationTitle.swift b/Core/Core/View/Base/NavigationTitle.swift new file mode 100644 index 000000000..7cae61193 --- /dev/null +++ b/Core/Core/View/Base/NavigationTitle.swift @@ -0,0 +1,52 @@ +// +// NavigationTitle.swift +// Core +// +// Created by  Stepanok Ivan on 29.05.2024. +// + +import SwiftUI +import Theme + +public struct NavigationTitle: View { + + private let title: String + private let backAction: () -> Void + + @Environment(\.isHorizontal) private var isHorizontal + + public init(title: String, backAction: @escaping () -> Void) { + self.title = title + self.backAction = backAction + } + + public var body: some View { + // MARK: - Navigation and Title + ZStack { + HStack { + Text(title) + .titleSettings(color: Theme.Colors.loginNavigationText) + .accessibilityIdentifier("\(title)_text") + } + VStack { + BackNavigationButton( + color: Theme.Colors.loginNavigationText, + action: { + backAction() + } + ) + .backViewStyle() + .foregroundColor(Theme.Colors.styledButtonText) + .padding(.leading, isHorizontal ? 48 : 0) + .accessibilityIdentifier("back_button") + + }.frame(minWidth: 0, + maxWidth: .infinity, + alignment: .topLeading) + } + } +} + +#Preview { + NavigationTitle(title: "Title", backAction: {}) +} diff --git a/Core/Core/View/Base/StyledButton.swift b/Core/Core/View/Base/StyledButton.swift index f76252e44..6aa2962f4 100644 --- a/Core/Core/View/Base/StyledButton.swift +++ b/Core/Core/View/Base/StyledButton.swift @@ -22,6 +22,7 @@ public struct StyledButton: View { private let buttonColor: Color private let textColor: Color private let isActive: Bool + private let horizontalPadding: Bool private let borderColor: Color private let iconImage: Image? private let iconPosition: IconImagePosition @@ -34,7 +35,8 @@ public struct StyledButton: View { borderColor: Color = .clear, iconImage: Image? = nil, iconPosition: IconImagePosition = .none, - isActive: Bool = true) { + isActive: Bool = true, + horizontalPadding: Bool = false) { self.title = title self.action = action self.isTransparent = isTransparent @@ -44,6 +46,7 @@ public struct StyledButton: View { self.isActive = isActive self.iconImage = iconImage self.iconPosition = iconPosition + self.horizontalPadding = horizontalPadding } public var body: some View { @@ -69,6 +72,7 @@ public struct StyledButton: View { } Spacer() } + .padding(.horizontal, horizontalPadding ? 20 : 0) } .disabled(!isActive) .frame(maxWidth: idiom == .pad ? 260: .infinity, minHeight: isTransparent ? 36 : 42) diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 19a8768f7..3ef1aac7a 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -216,6 +216,12 @@ class ScreenAssembly: Assembly { ) } + container.register(DatesAndCalendarViewModel.self) { r in + DatesAndCalendarViewModel( + router: r.resolve(ProfileRouter.self)! + ) + } + container.register(ManageAccountViewModel.self) { r in ManageAccountViewModel( router: r.resolve(ProfileRouter.self)!, diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index cd0bd192e..a6e175dff 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -714,6 +714,27 @@ public class Router: AuthorizationRouter, navigationController.pushViewController(controller, animated: true) } + public func showDatesAndCalendar() { + let viewModel = Container.shared.resolve(DatesAndCalendarViewModel.self)! + let view = DatesAndCalendarView(viewModel: viewModel) + let controller = UIHostingController(rootView: view) + navigationController.pushViewController(controller, animated: true) + } + + public func showSyncCalendarOptions() { + let viewModel = Container.shared.resolve(DatesAndCalendarViewModel.self)! + let view = SyncCalendarOptionsView(viewModel: viewModel) + let controller = UIHostingController(rootView: view) + navigationController.pushViewController(controller, animated: true) + } + + public func showCoursesToSync() { + let viewModel = Container.shared.resolve(DatesAndCalendarViewModel.self)! + let view = CoursesToSyncView(viewModel: viewModel) + let controller = UIHostingController(rootView: view) + navigationController.pushViewController(controller, animated: true) + } + public func showManageAccount() { let viewModel = Container.shared.resolve(ManageAccountViewModel.self)! let view = ManageAccountView(viewModel: viewModel) diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index a9a09fc33..a0f53c75d 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 020306C82932B13F000949EA /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020306C72932B13F000949EA /* EditProfileView.swift */; }; 020306CA2932B14D000949EA /* EditProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020306C92932B14D000949EA /* EditProfileViewModel.swift */; }; 020CBD8D2BC53E1B003D6B4E /* VideoSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020CBD8C2BC53E1B003D6B4E /* VideoSettingsView.swift */; }; + 021C90D52BC986B3004876AF /* DatesAndCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021C90D42BC986B3004876AF /* DatesAndCalendarView.swift */; }; + 021C90D72BC98734004876AF /* DatesAndCalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021C90D62BC98734004876AF /* DatesAndCalendarViewModel.swift */; }; 021D924628DC634300ACC565 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D924528DC634300ACC565 /* ProfileView.swift */; }; 021D924C28DC884A00ACC565 /* ProfileEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D924B28DC884A00ACC565 /* ProfileEndpoint.swift */; }; 021D924E28DC88BB00ACC565 /* ProfileRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D924D28DC88BB00ACC565 /* ProfileRepository.swift */; }; @@ -18,6 +20,9 @@ 021D925528DC92F800ACC565 /* ProfileInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D925428DC92F800ACC565 /* ProfileInteractor.swift */; }; 021D925C28DDADBD00ACC565 /* swiftgen.yml in Resources */ = {isa = PBXBuildFile; fileRef = 021D925B28DDADBD00ACC565 /* swiftgen.yml */; }; 021D925F28DDADE600ACC565 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 021D926128DDADE600ACC565 /* Localizable.strings */; }; + 022301E42BF4B7610028A287 /* SyncCalendarOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022301E32BF4B7610028A287 /* SyncCalendarOptionsView.swift */; }; + 022301E62BF4B7A20028A287 /* AssignmentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022301E52BF4B7A20028A287 /* AssignmentStatusView.swift */; }; + 022301E82BF4C4E70028A287 /* ToggleWithDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022301E72BF4C4E70028A287 /* ToggleWithDescriptionView.swift */; }; 0248F9B128DDB09D0041327E /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0248F9B028DDB09D0041327E /* Strings.swift */; }; 0259104429C39C9E004B5A55 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0259104329C39C9E004B5A55 /* SettingsView.swift */; }; 0259104629C39CCF004B5A55 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0259104529C39CCF004B5A55 /* SettingsViewModel.swift */; }; @@ -25,6 +30,8 @@ 025DE1A028DB4D9D0053E0F4 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 025DE19F28DB4D9D0053E0F4 /* Core.framework */; }; 0262149229AE57A1008BD75A /* DeleteAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262149129AE57A1008BD75A /* DeleteAccountView.swift */; }; 0262149429AE57B1008BD75A /* DeleteAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262149329AE57B1008BD75A /* DeleteAccountViewModel.swift */; }; + 0281D1532BEA9A40006DAD7A /* NewCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0281D1522BEA9A40006DAD7A /* NewCalendarView.swift */; }; + 0281D1552BEBA8D9006DAD7A /* DropDownPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0281D1542BEBA8D9006DAD7A /* DropDownPicker.swift */; }; 0288C4EA2BC6AE2D009158B9 /* ManageAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0288C4E92BC6AE2D009158B9 /* ManageAccountView.swift */; }; 0288C4EC2BC6AE82009158B9 /* ManageAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0288C4EB2BC6AE82009158B9 /* ManageAccountViewModel.swift */; }; 029301DA2938948500E99AB8 /* ProfileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029301D92938948500E99AB8 /* ProfileType.swift */; }; @@ -37,6 +44,9 @@ 02D0FD0B2AD6984D0020D752 /* UserProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D0FD0A2AD6984D0020D752 /* UserProfileViewModel.swift */; }; 02F175352A4DAD030019CD70 /* ProfileAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F175342A4DAD030019CD70 /* ProfileAnalytics.swift */; }; 02F3BFE7292539850051930C /* ProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFE6292539850051930C /* ProfileRouter.swift */; }; + 02F81DDF2BF4D83E002D3604 /* CalendarDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F81DDE2BF4D83E002D3604 /* CalendarDialogView.swift */; }; + 02F81DE12BF4F009002D3604 /* CoursesToSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F81DE02BF4F009002D3604 /* CoursesToSyncView.swift */; }; + 02F81DE32BF502B9002D3604 /* SyncSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F81DE22BF502B9002D3604 /* SyncSelector.swift */; }; 02FE9A802BC707D500B3C206 /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FE9A7F2BC707D400B3C206 /* SettingsViewModelTests.swift */; }; 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0796C8C829B7905300444B05 /* ProfileBottomSheet.swift */; }; 25B36FF48C1307888A3890DA /* Pods_App_Profile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEA369C38362C1A91A012F70 /* Pods_App_Profile.framework */; }; @@ -60,6 +70,8 @@ 020306C92932B14D000949EA /* EditProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileViewModel.swift; sourceTree = ""; }; 020CBD8C2BC53E1B003D6B4E /* VideoSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoSettingsView.swift; sourceTree = ""; }; 020F834A28DB4CCD0062FA70 /* Profile.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Profile.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 021C90D42BC986B3004876AF /* DatesAndCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesAndCalendarView.swift; sourceTree = ""; }; + 021C90D62BC98734004876AF /* DatesAndCalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesAndCalendarViewModel.swift; sourceTree = ""; }; 021D924528DC634300ACC565 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 021D924B28DC884A00ACC565 /* ProfileEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEndpoint.swift; sourceTree = ""; }; 021D924D28DC88BB00ACC565 /* ProfileRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRepository.swift; sourceTree = ""; }; @@ -67,6 +79,9 @@ 021D925428DC92F800ACC565 /* ProfileInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileInteractor.swift; sourceTree = ""; }; 021D925B28DDADBD00ACC565 /* swiftgen.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; 021D926028DDADE600ACC565 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 022301E32BF4B7610028A287 /* SyncCalendarOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncCalendarOptionsView.swift; sourceTree = ""; }; + 022301E52BF4B7A20028A287 /* AssignmentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssignmentStatusView.swift; sourceTree = ""; }; + 022301E72BF4C4E70028A287 /* ToggleWithDescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleWithDescriptionView.swift; sourceTree = ""; }; 0248F9B028DDB09D0041327E /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 0259104329C39C9E004B5A55 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 0259104529C39CCF004B5A55 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; @@ -74,6 +89,8 @@ 025DE19F28DB4D9D0053E0F4 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0262149129AE57A1008BD75A /* DeleteAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountView.swift; sourceTree = ""; }; 0262149329AE57B1008BD75A /* DeleteAccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountViewModel.swift; sourceTree = ""; }; + 0281D1522BEA9A40006DAD7A /* NewCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewCalendarView.swift; sourceTree = ""; }; + 0281D1542BEBA8D9006DAD7A /* DropDownPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownPicker.swift; sourceTree = ""; }; 0288C4E92BC6AE2D009158B9 /* ManageAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAccountView.swift; sourceTree = ""; }; 0288C4EB2BC6AE82009158B9 /* ManageAccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAccountViewModel.swift; sourceTree = ""; }; 029301D92938948500E99AB8 /* ProfileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileType.swift; sourceTree = ""; }; @@ -87,6 +104,9 @@ 02ED50CE29A64BAD008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F175342A4DAD030019CD70 /* ProfileAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAnalytics.swift; sourceTree = ""; }; 02F3BFE6292539850051930C /* ProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRouter.swift; sourceTree = ""; }; + 02F81DDE2BF4D83E002D3604 /* CalendarDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDialogView.swift; sourceTree = ""; }; + 02F81DE02BF4F009002D3604 /* CoursesToSyncView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoursesToSyncView.swift; sourceTree = ""; }; + 02F81DE22BF502B9002D3604 /* SyncSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncSelector.swift; sourceTree = ""; }; 02FE9A7F2BC707D400B3C206 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SettingsViewModelTests.swift; path = ProfileTests/Presentation/Settings/SettingsViewModelTests.swift; sourceTree = SOURCE_ROOT; }; 0796C8C829B7905300444B05 /* ProfileBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileBottomSheet.swift; sourceTree = ""; }; 0E5054C44435557666B6D885 /* Pods-App-Profile.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.debugstage.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.debugstage.xcconfig"; sourceTree = ""; }; @@ -188,9 +208,22 @@ path = Profile; sourceTree = ""; }; + 021C90D32BC986A4004876AF /* DatesAndCalendar */ = { + isa = PBXGroup; + children = ( + 0281D1512BEA9A2D006DAD7A /* Elements */, + 021C90D42BC986B3004876AF /* DatesAndCalendarView.swift */, + 022301E32BF4B7610028A287 /* SyncCalendarOptionsView.swift */, + 02F81DE02BF4F009002D3604 /* CoursesToSyncView.swift */, + 021C90D62BC98734004876AF /* DatesAndCalendarViewModel.swift */, + ); + path = DatesAndCalendar; + sourceTree = ""; + }; 021D924428DC631800ACC565 /* Presentation */ = { isa = PBXGroup; children = ( + 021C90D32BC986A4004876AF /* DatesAndCalendar */, 0259104229C39C84004B5A55 /* Settings */, 0203DC3D29AE79F80017BD05 /* Profile */, 0203DC3C29AE79EB0017BD05 /* EditProfile */, @@ -265,6 +298,19 @@ path = DeleteAccount; sourceTree = ""; }; + 0281D1512BEA9A2D006DAD7A /* Elements */ = { + isa = PBXGroup; + children = ( + 0281D1522BEA9A40006DAD7A /* NewCalendarView.swift */, + 0281D1542BEBA8D9006DAD7A /* DropDownPicker.swift */, + 022301E52BF4B7A20028A287 /* AssignmentStatusView.swift */, + 022301E72BF4C4E70028A287 /* ToggleWithDescriptionView.swift */, + 02F81DDE2BF4D83E002D3604 /* CalendarDialogView.swift */, + 02F81DE22BF502B9002D3604 /* SyncSelector.swift */, + ); + path = Elements; + sourceTree = ""; + }; 02A4832F29B770B600D33F33 /* Profile */ = { isa = PBXGroup; children = ( @@ -585,13 +631,21 @@ files = ( 021D924E28DC88BB00ACC565 /* ProfileRepository.swift in Sources */, 0288C4EC2BC6AE82009158B9 /* ManageAccountViewModel.swift in Sources */, + 0281D1552BEBA8D9006DAD7A /* DropDownPicker.swift in Sources */, 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */, 021D924C28DC884A00ACC565 /* ProfileEndpoint.swift in Sources */, + 02F81DE12BF4F009002D3604 /* CoursesToSyncView.swift in Sources */, + 0281D1532BEA9A40006DAD7A /* NewCalendarView.swift in Sources */, BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfoView.swift in Sources */, + 022301E82BF4C4E70028A287 /* ToggleWithDescriptionView.swift in Sources */, + 022301E42BF4B7610028A287 /* SyncCalendarOptionsView.swift in Sources */, + 021C90D52BC986B3004876AF /* DatesAndCalendarView.swift in Sources */, 020306C82932B13F000949EA /* EditProfileView.swift in Sources */, 0262149229AE57A1008BD75A /* DeleteAccountView.swift in Sources */, + 02F81DE32BF502B9002D3604 /* SyncSelector.swift in Sources */, 02D0FD092AD698380020D752 /* UserProfileView.swift in Sources */, 0259104629C39CCF004B5A55 /* SettingsViewModel.swift in Sources */, + 021C90D72BC98734004876AF /* DatesAndCalendarViewModel.swift in Sources */, 021D925528DC92F800ACC565 /* ProfileInteractor.swift in Sources */, 029301DA2938948500E99AB8 /* ProfileType.swift in Sources */, 0259104429C39C9E004B5A55 /* SettingsView.swift in Sources */, @@ -601,11 +655,13 @@ 020306CA2932B14D000949EA /* EditProfileViewModel.swift in Sources */, 0259104829C3A5F0004B5A55 /* VideoQualityView.swift in Sources */, 0288C4EA2BC6AE2D009158B9 /* ManageAccountView.swift in Sources */, + 022301E62BF4B7A20028A287 /* AssignmentStatusView.swift in Sources */, 021D924628DC634300ACC565 /* ProfileView.swift in Sources */, 02F3BFE7292539850051930C /* ProfileRouter.swift in Sources */, 02D0FD0B2AD6984D0020D752 /* UserProfileViewModel.swift in Sources */, 020CBD8D2BC53E1B003D6B4E /* VideoSettingsView.swift in Sources */, 02F175352A4DAD030019CD70 /* ProfileAnalytics.swift in Sources */, + 02F81DDF2BF4D83E002D3604 /* CalendarDialogView.swift in Sources */, 0262149429AE57B1008BD75A /* DeleteAccountViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift new file mode 100644 index 000000000..5ba8b2944 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift @@ -0,0 +1,134 @@ +// +// CoursesToSyncView.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Theme +import Core + +public struct CoursesToSyncView: View { + + @ObservedObject + private var viewModel: DatesAndCalendarViewModel + + @Environment(\.isHorizontal) private var isHorizontal + + public init(viewModel: DatesAndCalendarViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + GeometryReader { proxy in + ZStack(alignment: .top) { + ThemeAssets.headerBackground.swiftUIImage + .resizable() + .edgesIgnoringSafeArea(.top) + .frame(maxWidth: .infinity, maxHeight: 200) + .accessibilityIdentifier("title_bg_image") + + VStack(alignment: .leading, spacing: 8) { + // MARK: Navigation and Title + NavigationTitle( + title: ProfileLocalization.CoursesToSync.title, + backAction: { + viewModel.router.back() + } + ) + + // MARK: Body + ScrollView { + Group { + Text(ProfileLocalization.CoursesToSync.description) + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.top, 24) + .padding(.horizontal, 24) + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .leading + ) + + ToggleWithDescriptionView( + text: ProfileLocalization.CoursesToSync.hideInactiveCourses, + description: ProfileLocalization.CoursesToSync.hideInactiveCoursesDescription, + toggle: $viewModel.hideInactiveCourses + ) + .padding(.horizontal, 24) + .padding(.vertical, 16) + + SyncSelector(sync: $viewModel.synced) + .padding(.horizontal, 24) + + coursesList + } + .padding(.horizontal, isHorizontal ? 48 : 0) + } + .frameLimit(width: proxy.size.width) + .roundedBackground(Theme.Colors.background) + .ignoresSafeArea(.all, edges: .bottom) + } + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + + if viewModel.showError { + ErrorAlertView(errorMessage: $viewModel.errorMessage) + } + } + .ignoresSafeArea(.all, edges: .horizontal) + } + } + + private var coursesList: some View { + VStack(alignment: .leading, spacing: 24) { + ForEach( + Array( + viewModel.coursesForSync.filter({ course in + course.synced == viewModel.synced && (!viewModel.hideInactiveCourses || course.active) + }).enumerated() + ), + id: \.offset + ) { _, course in + HStack { + CheckBoxView( + checked: Binding( + get: { course.synced }, + set: { _ in viewModel.toggleSync(for: course) } + ), + text: course.name, + color: Theme.Colors.textPrimary.opacity(course.active ? 1 : 0.8) + ) + + if !course.active { + Text(ProfileLocalization.CoursesToSync.inactive) + .font(Theme.Fonts.labelSmall) + .foregroundStyle(Theme.Colors.textPrimary.opacity(0.8)) + } + } + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .leading + ) + } + } + .padding(.horizontal, 24) + .padding(.vertical, 16) + } +} + +#if DEBUG +struct CoursesToSyncView_Previews: PreviewProvider { + static var previews: some View { + let vm = DatesAndCalendarViewModel( + router: ProfileRouterMock() + ) + return CoursesToSyncView(viewModel: vm) + .previewDisplayName("Courses to Sync") + .loadFonts() + } +} +#endif diff --git a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift new file mode 100644 index 000000000..a1ab29968 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift @@ -0,0 +1,195 @@ +// +// DatesAndCalendarView.swift +// Profile +// +// Created by  Stepanok Ivan on 12.04.2024. +// + +import SwiftUI +import Theme +import Core + +public struct DatesAndCalendarView: View { + + @ObservedObject + private var viewModel: DatesAndCalendarViewModel + + @State private var screenDimmed: Bool = false + + @Environment(\.isHorizontal) private var isHorizontal + + public init(viewModel: DatesAndCalendarViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + GeometryReader { proxy in + ZStack(alignment: .top) { + ThemeAssets.headerBackground.swiftUIImage + .resizable() + .edgesIgnoringSafeArea(.top) + .frame(maxWidth: .infinity, maxHeight: 200) + .accessibilityIdentifier("title_bg_image") + + VStack { + // MARK: Navigation and Title + NavigationTitle( + title: ProfileLocalization.DatesAndCalendar.title, + backAction: { + viewModel.router.back() + } + ) + + // MARK: Body + ScrollView { + Group { + calendarSyncCard +// relativeDatesToggle + } + .padding(.horizontal, isHorizontal ? 48 : 0) + } + .frameLimit(width: proxy.size.width) + .roundedBackground(Theme.Colors.background) + .ignoresSafeArea(.all, edges: .bottom) + } + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + + // Error Alert if needed + if viewModel.showError { + ErrorAlertView(errorMessage: $viewModel.errorMessage) + } + if screenDimmed { + Color.black.opacity(0.3) + .ignoresSafeArea() + .onTapGesture { + viewModel.openNewCalendarView = false + screenDimmed = false + } + } + if viewModel.openNewCalendarView { + NewCalendarView( + title: .newCalendar, + viewModel: viewModel, + beginSyncingTapped: { + if viewModel.calendarName == "" { + viewModel.calendarName = viewModel.calendarNameHint + } + viewModel.router.showSyncCalendarOptions() }, + onCloseTapped: { + viewModel.openNewCalendarView = false + screenDimmed = false + } + ) + .transition(.move(edge: .bottom)) + .frame(alignment: .center) + .onAppear { + screenDimmed = true + } + } + + if viewModel.showCalendaAccessDenided { + CalendarDialogView( + type: .calendarAccess, + action: { + viewModel.showCalendaAccessDenided = false + screenDimmed = false + viewModel.openAppSettings() + }, + onCloseTapped: { + viewModel.showCalendaAccessDenided = false + screenDimmed = false + } + ) + .transition(.move(edge: .bottom)) + .frame(alignment: .center) + .onAppear { + screenDimmed = true + } + } + + } + .ignoresSafeArea(.all, edges: .horizontal) + } + } + + // MARK: - Calendar Sync Card + private var calendarSyncCard: some View { + VStack(alignment: .leading) { + Text(ProfileLocalization.CalendarSync.title) + .multilineTextAlignment(.leading) + .padding(.top, 24) + .padding(.horizontal, 24) + .font(Theme.Fonts.bodyMedium) + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .center, spacing: 16) { + CoreAssets.calendarSyncIcon.swiftUIImage + .foregroundStyle(Theme.Colors.textPrimary) + .padding(.bottom, 16) + + Text(ProfileLocalization.CalendarSync.title) + .font(Theme.Fonts.bodyLarge) + .bold() + .foregroundColor(Theme.Colors.textPrimary) + .accessibilityIdentifier("calendar_sync_title") + + Text(ProfileLocalization.CalendarSync.description) + .font(Theme.Fonts.bodyMedium) + .foregroundColor(Theme.Colors.textPrimary) + .accessibilityIdentifier("calendar_sync_description") + + StyledButton(ProfileLocalization.CalendarSync.button, action: { + viewModel.requestCalendarPermission() + }, horizontalPadding: true) + .fixedSize() + .accessibilityIdentifier("calendar_sync_button") + } + .frame(minWidth: 0, + maxWidth: .infinity, + alignment: .top) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, alignment: .topLeading) + .padding(.top, 24) + .cardStyle(bgColor: Theme.Colors.textInputUnfocusedBackground, strokeColor: .clear) + } + } + + // MARK: - Options Toggle + private var relativeDatesToggle: some View { + VStack(alignment: .leading) { + Text(ProfileLocalization.Options.title) + .font(Theme.Fonts.labelLarge) + .foregroundColor(Theme.Colors.textPrimary) + HStack(spacing: 16) { + Toggle("", isOn: $viewModel.useRelativeDates) + .frame(width: 50) + .tint(Theme.Colors.accentColor) + Text(ProfileLocalization.Options.useRelativeDates) + .font(Theme.Fonts.bodyLarge) + .foregroundColor(Theme.Colors.textPrimary) + } + Text(ProfileLocalization.Options.showRelativeDates) + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + } + .padding(.horizontal, 24) + .frame(minWidth: 0, + maxWidth: .infinity, + alignment: .top) + .accessibilityIdentifier("relative_dates_toggle") + } +} + +#if DEBUG +struct DatesAndCalendarView_Previews: PreviewProvider { + static var previews: some View { + let vm = DatesAndCalendarViewModel( + router: ProfileRouterMock() + ) + DatesAndCalendarView(viewModel: vm) + .loadFonts() + } +} +#endif + diff --git a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift new file mode 100644 index 000000000..a87dff139 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarViewModel.swift @@ -0,0 +1,131 @@ +// +// DatesAndCalendarViewModel.swift +// Profile +// +// Created by  Stepanok Ivan on 12.04.2024. +// + +import SwiftUI +import Combine +import Core +import EventKit +import Theme + +public struct CourseForSync: Identifiable { + public let id: UUID + public let name: String + public var synced: Bool + public var active: Bool + + public init(id: UUID = UUID(), name: String, synced: Bool, active: Bool) { + self.id = id + self.name = name + self.synced = synced + self.active = active + } +} + +public class DatesAndCalendarViewModel: ObservableObject { + // Output + @Published var useRelativeDates: Bool = false + @Published var showCalendaAccessDenided: Bool = false + @Published var showError: Bool = false + @Published var errorMessage: String? + @Published var openNewCalendarView: Bool = false + + // NewCalendarView + @Published var accountSelection: DropDownPicker.DownPickerOption? = .init( + title: ProfileLocalization.Calendar.Dropdown.icloud + ) + var calendarNameHint: String + @Published var calendarName: String = "" + @Published var colorSelection: DropDownPicker.DownPickerOption? = .init( + title: ProfileLocalization.Calendar.DropdownColor.accent, + color: Theme.Colors.accentColor + ) + + // SyncCalendarOptions + @Published var assignmentStatus: AssignmentStatus = .synced + @Published var courseCalendarSync: Bool = false + @Published var reconnectRequired: Bool = false + @Published var openChangeSyncView: Bool = false + + let accounts: [DropDownPicker.DownPickerOption] = [ + .init(title: ProfileLocalization.Calendar.Dropdown.icloud), + .init(title: ProfileLocalization.Calendar.Dropdown.local) + ] + let colors: [DropDownPicker.DownPickerOption] = [ + .init(title: ProfileLocalization.Calendar.DropdownColor.accent, color: Theme.Colors.accentColor), + .init(title: ProfileLocalization.Calendar.DropdownColor.red, color: .red), + .init(title: ProfileLocalization.Calendar.DropdownColor.orange, color: .orange), + .init(title: ProfileLocalization.Calendar.DropdownColor.yellow, color: .yellow), + .init(title: ProfileLocalization.Calendar.DropdownColor.green, color: .green), + .init(title: ProfileLocalization.Calendar.DropdownColor.blue, color: .blue), + .init(title: ProfileLocalization.Calendar.DropdownColor.purple, color: .purple), + .init(title: ProfileLocalization.Calendar.DropdownColor.brown, color: .brown) + ] + + var router: ProfileRouter + + // CoursesToSyncView + + @Published var coursesForSync = [ + CourseForSync(name: "History of Example Studies", synced: true, active: true), + CourseForSync(name: "Example Language 101", synced: true, active: true), + CourseForSync(name: "Example Course", synced: true, active: true), + CourseForSync(name: "More Example Courses", synced: true, active: true), + CourseForSync(name: "Another Example Course", synced: true, active: true), + CourseForSync(name: "Example Excluded Course", synced: false, active: false), + CourseForSync(name: "Science of Examples", synced: false, active: true), + CourseForSync(name: "Example Learning", synced: false, active: false), + CourseForSync(name: "Science of Examples", synced: false, active: false) + ] + + @Published var synced: Bool = true + @Published var hideInactiveCourses: Bool = false + + func toggleSync(for course: CourseForSync) { + if let index = coursesForSync.firstIndex(where: { $0.id == course.id }) { + if coursesForSync[index].active { + coursesForSync[index].synced.toggle() + } + } + } + + public init(router: ProfileRouter) { + self.router = router + self.calendarNameHint = ProfileLocalization.Calendar.courseDates((Bundle.main.applicationName ?? "")) + } + + // MARK: - Request Calendar Permission + func requestCalendarPermission() { + let eventStore = EKEventStore() + eventStore.requestAccess(to: .event) { [weak self] granted, error in + DispatchQueue.main.async { + if granted { + self?.showNewCalendarSetup() + } else { + self?.showCalendarAccessDenided() + } + } + } + } + + func openAppSettings() { + if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + + private func showCalendarAccessDenided() { + withAnimation(.bouncy(duration: 0.3)) { + self.showCalendaAccessDenided = true + } + } + + private func showNewCalendarSetup() { + withAnimation(.bouncy(duration: 0.3)) { + openNewCalendarView = true + } + } +} diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/AssignmentStatusView.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/AssignmentStatusView.swift new file mode 100644 index 000000000..b1451a997 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/AssignmentStatusView.swift @@ -0,0 +1,90 @@ +// +// AssignmentStatusView.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Core +import Theme + +enum AssignmentStatus { + case synced + case failed + case offline + + var statusText: String { + switch self { + case .synced: + ProfileLocalization.AssignmentStatus.synced + case .failed: + ProfileLocalization.AssignmentStatus.failed + case .offline: + ProfileLocalization.AssignmentStatus.offline + } + } + + var image: Image { + switch self { + case .synced: + CoreAssets.synced.swiftUIImage + case .failed: + CoreAssets.syncFailed.swiftUIImage + case .offline: + CoreAssets.syncOffline.swiftUIImage + } + } +} + +struct AssignmentStatusView: View { + + private let title: String + @Binding private var status: AssignmentStatus + private let calendarColor: Color + + init(title: String, status: Binding, calendarColor: Color) { + self.title = title + self._status = status + self.calendarColor = calendarColor + } + + var body: some View { + ZStack { + HStack { + Circle() + .frame(width: 18, height: 18) + .foregroundStyle(calendarColor) + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(Theme.Fonts.labelLarge) + .foregroundStyle(Theme.Colors.textPrimary) + Text(status.statusText) + .font(Theme.Fonts.labelSmall) + .foregroundStyle(Theme.Colors.textSecondary) + } + .padding(.vertical, 10) + .multilineTextAlignment(.leading) + Spacer() + status.image + } + + .padding(.horizontal, 16) + } + .background( + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Theme.Colors.textInputUnfocusedBackground) + ) + } +} + +#if DEBUG +#Preview { + AssignmentStatusView( + title: "My Assignments", + status: .constant(.synced), + calendarColor: .blue + ) + .loadFonts() +} +#endif diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/CalendarDialogView.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/CalendarDialogView.swift new file mode 100644 index 000000000..c8a6b88db --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/CalendarDialogView.swift @@ -0,0 +1,187 @@ +// +// CalendarDialogView.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Core +import Theme + +struct CalendarDialogView: View { + + enum CalendarDialogType { + case calendarAccess + case disableCalendarSync + + var title: String { + switch self { + case .calendarAccess: + ProfileLocalization.CalendarDialog.calendarAccess + case .disableCalendarSync: + ProfileLocalization.CalendarDialog.disableCalendarSync + } + } + + var description: String { + switch self { + case .calendarAccess: + ProfileLocalization.CalendarDialog.calendarAccessDescription + case .disableCalendarSync: + ProfileLocalization.CalendarDialog.disableCalendarSyncDescription + } + } + } + + @Environment(\.isHorizontal) private var isHorizontal + private var onCloseTapped: (() -> Void) = {} + private var action: (() -> Void) = {} + private let type: CalendarDialogType + private let calendarCircleColor: Color? + private let calendarName: String? + + init( + type: CalendarDialogType, + calendarCircleColor: Color? = nil, + calendarName: String? = nil, + action: @escaping () -> Void, + onCloseTapped: @escaping () -> Void + ) { + self.type = type + self.calendarCircleColor = calendarCircleColor + self.calendarName = calendarName + self.action = action + self.onCloseTapped = onCloseTapped + } + + var body: some View { + ZStack { + Color.clear + .ignoresSafeArea() + if isHorizontal { + ScrollView { + content + .frame(maxWidth: 400) + .padding(24) + .background( + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Theme.Colors.background) + ) + .padding(24) + } + } else { + content + .frame(maxWidth: 400) + .padding(24) + .background( + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Theme.Colors.background) + ) + .padding(24) + } + } + } + + private var content: some View { + VStack(alignment: .leading, spacing: 16) { + HStack(alignment: .center) { + CoreAssets.warningFilled.swiftUIImage + .resizable() + .frame(width: 24, height: 24) + Text(type.title) + .font(Theme.Fonts.titleLarge) + .bold() + Spacer() + Button(action: { + onCloseTapped() + }, label: { + Image(systemName: "xmark") + .resizable() + .frame(width: 12, height: 12) + }) + } + + if let calendarName, let calendarCircleColor { + HStack { + Circle() + .frame(width: 18, height: 18) + .foregroundStyle(calendarCircleColor) + Text(calendarName) + .strikethrough() + .font(Theme.Fonts.labelLarge) + .foregroundStyle(Theme.Colors.textPrimary) + .multilineTextAlignment(.leading) + Spacer() + } + .padding(16) + .background( + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Theme.Colors.textInputUnfocusedBackground) + ) + } + + Text(type.description) + .font(Theme.Fonts.bodySmall) + .foregroundColor(Theme.Colors.textPrimary) + .multilineTextAlignment(.leading) + + VStack(spacing: 16) { + switch type { + case .calendarAccess: + StyledButton( + ProfileLocalization.CalendarDialog.grantCalendarAccess, + action: { + action() + }, + iconImage: CoreAssets.calendarAccess.swiftUIImage, + iconPosition: .right + ) + StyledButton( + ProfileLocalization.CalendarDialog.cancel, + action: { + onCloseTapped() + }, + color: Theme.Colors.background, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.accentColor + ) + case .disableCalendarSync: + StyledButton( + ProfileLocalization.CalendarDialog.disableSyncing, + action: { + action() + }, + color: Theme.Colors.background, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.accentColor + ) + + StyledButton(ProfileLocalization.CalendarDialog.cancel) { + onCloseTapped() + } + } + } + .padding(.top, 16) + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .center + ) + } + .frame(maxWidth: 360) + } +} + +#if DEBUG +#Preview { + CalendarDialogView( + type: .calendarAccess, + calendarCircleColor: .blue, + calendarName: "My Assignments", + action: {}, + onCloseTapped: {} + ) + .loadFonts() +} +#endif diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift new file mode 100644 index 000000000..bef40545b --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/DropDownPicker.swift @@ -0,0 +1,184 @@ +// +// DropDownPicker.swift +// Profile +// +// Created by  Stepanok Ivan on 08.05.2024. +// + +import SwiftUI +import Core +import Theme + +enum DropDownPickerState { + case top + case bottom +} + +struct DropDownPicker: View { + + struct DownPickerOption: Hashable { + let title: String + let color: Color? + + init(title: String, color: Color? = nil) { + self.title = title + self.color = color + } + + func hash(into hasher: inout Hasher) { + hasher.combine(title) + } + + static func == (lhs: DownPickerOption, rhs: DownPickerOption) -> Bool { + lhs.title == rhs.title + } + } + + @Binding var selection: DownPickerOption? + var state: DropDownPickerState = .bottom + var options: [DownPickerOption] + + @State var showDropdown = false + + @State private var index = 1000.0 + @State var zindex = 1000.0 + + init(selection: Binding, state: DropDownPickerState, options: [DownPickerOption]) { + self._selection = selection + self.state = state + self.options = options + } + + var body: some View { + GeometryReader { + let size = $0.size + VStack(spacing: 0) { + if state == .top && showDropdown { + optionsView() + } + HStack { + if let color = selection?.color { + Circle() + .frame(width: 18, height: 18) + .foregroundStyle(color) + } + Text( + selection == nil + ? ProfileLocalization.DropDownPicker.select + : selection!.title + ) + .foregroundStyle(Theme.Colors.textPrimary) + .font(Theme.Fonts.bodyMedium) + Spacer(minLength: 0) + Image(systemName: state == .top ? "chevron.up" : "chevron.down") + .foregroundColor(Theme.Colors.textPrimary) + .rotationEffect(.degrees((showDropdown ? -180 : 0))) + } + .padding(.horizontal, 15) + .contentShape(.rect) + .onTapGesture { + index += 1 + zindex = index + withAnimation(.bouncy(duration: 0.2)) { + showDropdown.toggle() + } + } + .zIndex(10) + .frame(height: 48) + .background(Theme.Colors.background) + .overlay { + RoundedRectangle(cornerRadius: 8) + .stroke(Theme.Colors.textInputStroke, lineWidth: 1) + .padding(1) + } + + if state == .bottom && showDropdown { + optionsView() + .overlay { + RoundedRectangle(cornerRadius: 8) + .stroke(Theme.Colors.textInputStroke, lineWidth: 1) + .padding(1) + } + .padding(.top, 4) + } + } + .clipped() + .background(Theme.Colors.background) + .cornerRadius(8) + .frame(height: size.height, alignment: state == .top ? .bottom : .top) + .onTapBackground(enabled: showDropdown, { showDropdown = false }) + } + .frame(height: 48) + .zIndex(zindex) + } + + func optionsView() -> some View { + + func menuHeight() -> Double { + if options.count < 3 { + return Double(options.count * 56) + } else { + return 200.0 + } + } + + return ScrollView { + VStack(spacing: 0) { + ForEach(options, id: \.self) { option in + ZStack { + HStack { + if let color = option.color { + Circle() + .frame(width: 18, height: 18) + .foregroundStyle(color) + } + Text(option.title) + .font(Theme.Fonts.bodyMedium) + .foregroundStyle(Theme.Colors.textPrimary) + Spacer() +// Image(systemName: "checkmark") +// .opacity(selection == option ? 1 : 0) + } + VStack { + Spacer() + if option != options.last { + Theme.Colors.textInputStroke + .frame(height: 1) + .padding(.top, 8) + .frame(alignment: .bottom) + } + } + } + .foregroundStyle(selection == option ? Color.primary : Color.gray) + .animation(.easeIn(duration: 0.2), value: selection) + .frame(height: 56) + .contentShape(.rect) + .padding(.horizontal, 15) + .onTapGesture { + withAnimation(.easeIn(duration: 0.2)) { + selection = option + showDropdown.toggle() + } + } + } + } + .padding(.top, 4) + }.frame(height: menuHeight()) + .transition(.move(edge: state == .top ? .bottom : .top)) + .zIndex(1) + } +} + +#Preview { + DropDownPicker( + selection: .constant(.init(title: "Selected")), + state: .bottom, + options: [ + .init(title: "One"), + .init( + title: "Two" + ) + ] + ) + .loadFonts() +} diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/NewCalendarView.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/NewCalendarView.swift new file mode 100644 index 000000000..64d9342a1 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/NewCalendarView.swift @@ -0,0 +1,154 @@ +// +// NewCalendarView.swift +// Profile +// +// Created by  Stepanok Ivan on 07.05.2024. +// + +import SwiftUI +import Core +import Theme + +struct NewCalendarView: View { + + enum Title { + case newCalendar + case changeSyncOptions + + var text: String { + switch self { + case .newCalendar: + ProfileLocalization.Calendar.newCalendar + case .changeSyncOptions: + ProfileLocalization.Calendar.changeSyncOptions + } + } + } + + @ObservedObject + private var viewModel: DatesAndCalendarViewModel + @Environment(\.isHorizontal) private var isHorizontal + private var beginSyncingTapped: (() -> Void) = {} + private var onCloseTapped: (() -> Void) = {} + + private let title: Title + + init( + title: Title, + viewModel: DatesAndCalendarViewModel, + beginSyncingTapped: @escaping () -> Void, + onCloseTapped: @escaping () -> Void + ) { + self.title = title + self.viewModel = viewModel + self.beginSyncingTapped = beginSyncingTapped + self.onCloseTapped = onCloseTapped + } + + var body: some View { + ZStack { + Color.clear + .ignoresSafeArea() + if isHorizontal { + ScrollView { + content + + } + } else { + content + } + } + } + + private var content: some View { + VStack(alignment: .leading) { + HStack(alignment: .center) { + Text(title.text) + .font(Theme.Fonts.titleLarge) + .bold() + Spacer() + Button(action: { + onCloseTapped() + }, label: { + Image(systemName: "xmark") + .resizable() + .frame(width: 12, height: 12) + }) + } + .padding(.bottom, 20) + Text(ProfileLocalization.Calendar.account) + .font(Theme.Fonts.bodySmall).bold() + DropDownPicker(selection: $viewModel.accountSelection, state: .bottom, options: viewModel.accounts) + + Text(ProfileLocalization.Calendar.calendarName) + .font(Theme.Fonts.bodySmall).bold() + .padding(.top, 16) + TextField(viewModel.calendarNameHint, text: $viewModel.calendarName) + .font(Theme.Fonts.bodyLarge) + .padding() + .background(Theme.Colors.background) + .cornerRadius(8) + .frame(height: 48) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Theme.Colors.textInputStroke, lineWidth: 1) + .padding(1) + ) + + Text(ProfileLocalization.Calendar.color) + .font(Theme.Fonts.bodySmall).bold() + .padding(.top, 16) + DropDownPicker(selection: $viewModel.colorSelection, state: .bottom, options: viewModel.colors) + + Text(ProfileLocalization.Calendar.upcomingAssignments) + .font(Theme.Fonts.bodySmall) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.vertical, 16) + .multilineTextAlignment(.center) + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .center + ) + + VStack(spacing: 16) { + StyledButton(ProfileLocalization.Calendar.cancel, + action: { + onCloseTapped() + }, + color: Theme.Colors.background, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.accentColor + ) + + StyledButton(ProfileLocalization.Calendar.beginSyncing) { + beginSyncingTapped() + } + } + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .center + ) + } + .frame(maxWidth: 360) + .padding(24) + .background( + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Theme.Colors.background) + ) + .padding(24) + } +} + +#if DEBUG +#Preview { + NewCalendarView( + title: .newCalendar, + viewModel: DatesAndCalendarViewModel(router: ProfileRouterMock()), + beginSyncingTapped: {}, + onCloseTapped: {} + ) + .loadFonts() +} +#endif diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/SyncSelector.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/SyncSelector.swift new file mode 100644 index 000000000..42ee8a552 --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/SyncSelector.swift @@ -0,0 +1,60 @@ +// +// SyncSelector.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Core +import Theme + +struct SyncSelector: View { + @Binding var sync: Bool + + var body: some View { + HStack(spacing: 2) { + Button(action: { + sync = true + }) { + Text(ProfileLocalization.SyncSelector.synced) + .font(Theme.Fonts.bodyMedium) + .frame(maxWidth: .infinity) + .padding() + .background(sync ? Theme.Colors.accentColor : Theme.Colors.background) + .foregroundColor(sync ? Theme.Colors.white : Theme.Colors.accentColor) + .clipShape(RoundedCorners(tl: 8, bl: 8)) + } + .overlay( + RoundedCorners(tl: 8, bl: 8) + .stroke(Theme.Colors.accentColor, lineWidth: 1) + .padding(.vertical, 0.5) + ) + Button(action: { + sync = false + }) { + Text(ProfileLocalization.SyncSelector.notSynced) + .font(Theme.Fonts.bodyMedium) + .frame(maxWidth: .infinity) + .padding() + .background(sync ? Theme.Colors.background : Theme.Colors.accentColor) + .foregroundColor(sync ? Theme.Colors.accentColor : Theme.Colors.white) + .clipShape(RoundedCorners(tr: 8, br: 8)) + } + .overlay( + RoundedCorners(tr: 8, br: 8) + .stroke(Theme.Colors.accentColor, lineWidth: 1) + .padding(.vertical, 0.5) + ) + } + + .frame(height: 42) + } +} + +#if DEBUG +#Preview { + SyncSelector(sync: .constant(true)) + .padding(8) +} +#endif diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/ToggleWithDescriptionView.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/ToggleWithDescriptionView.swift new file mode 100644 index 000000000..fed25a7df --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/ToggleWithDescriptionView.swift @@ -0,0 +1,98 @@ +// +// ToggleWithDescriptionView.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Theme +import Core + +struct ToggleWithDescriptionView: View { + + let text: String + let description: String + @Binding var toggle: Bool + @Binding var showAlertIcon: Bool + + init( + text: String, + description: String, + toggle: Binding, + showAlertIcon: Binding = .constant(false) + ) { + self.text = text + self.description = description + self._toggle = toggle + self._showAlertIcon = showAlertIcon + } + + var body: some View { + VStack(alignment: .leading, spacing: 18) { +// HStack(spacing: 12) { + Toggle(isOn: $toggle, label: { + HStack { + Text(text) + .font(Theme.Fonts.bodyLarge) + .foregroundColor(Theme.Colors.textPrimary) + if showAlertIcon { + CoreAssets.warningFilled.swiftUIImage + .resizable() + .frame(width: 24, height: 24) + } + } + }) + .tint(Theme.Colors.accentColor) +// CustomToggle(isOn: $toggle) +// .padding(.leading, 10) +// Text(text) +// .font(Theme.Fonts.bodyLarge) +// .foregroundColor(Theme.Colors.textPrimary) +// if showAlertIcon { +// CoreAssets.warningFilled.swiftUIImage +// .resizable() +// .frame(width: 24, height: 24) +// } +// } + Text(description) + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + } + .frame(minWidth: 0, + maxWidth: .infinity, + alignment: .leading) + .accessibilityIdentifier("\(text)_toggle") + } +} + +#Preview { + ToggleWithDescriptionView( + text: "Use relative dates", + description: "Show relative dates like “Tomorrow” and “Yesterday”", + toggle: .constant(true), + showAlertIcon: .constant(true) + ) + .loadFonts() +} + +struct CustomToggle: View { + @Binding var isOn: Bool + + var body: some View { + Button(action: { + isOn.toggle() + }) { + RoundedRectangle(cornerRadius: 10) + .fill(isOn ? Theme.Colors.accentColor : Color.gray) + .frame(width: 37, height: 20) + .overlay( + Circle() + .fill(Color.white) + .frame(width: 16, height: 16) + .offset(x: isOn ? 8 : -8) + .animation(.easeInOut(duration: 0.2), value: isOn) + ) + } + } +} diff --git a/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift new file mode 100644 index 000000000..ecb0f213d --- /dev/null +++ b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift @@ -0,0 +1,231 @@ +// +// SyncCalendarOptionsView.swift +// Profile +// +// Created by  Stepanok Ivan on 15.05.2024. +// + +import SwiftUI +import Theme +import Core + +public struct SyncCalendarOptionsView: View { + + @ObservedObject + private var viewModel: DatesAndCalendarViewModel + + @State private var screenDimmed: Bool = false + + @Environment(\.isHorizontal) private var isHorizontal + + public init(viewModel: DatesAndCalendarViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + GeometryReader { proxy in + ZStack(alignment: .top) { + ThemeAssets.headerBackground.swiftUIImage + .resizable() + .edgesIgnoringSafeArea(.top) + .frame(maxWidth: .infinity, maxHeight: 200) + .accessibilityIdentifier("title_bg_image") + + VStack(spacing: 8) { + // MARK: Navigation and Title + NavigationTitle( + title: ProfileLocalization.DatesAndCalendar.title, + backAction: { + viewModel.router.back() + } + ) + + // MARK: Body + ScrollView { + Group { + if let colorSelectionColor = viewModel.colorSelection?.color { + optionTitle(ProfileLocalization.CalendarSync.title) + .padding(.top, 24) + AssignmentStatusView( + title: viewModel.calendarName, + status: $viewModel.assignmentStatus, + calendarColor: colorSelectionColor + ) + .padding(.horizontal, 24) + } + ToggleWithDescriptionView( + text: ProfileLocalization.CourseCalendarSync.title, + description: viewModel.reconnectRequired + ? ProfileLocalization.CourseCalendarSync.Description.reconnectRequired + : ProfileLocalization.CourseCalendarSync.Description.syncing, + toggle: $viewModel.courseCalendarSync, + showAlertIcon: $viewModel.reconnectRequired + ) + .padding(.vertical, 24) + .padding(.horizontal, 24) + + StyledButton( + viewModel.reconnectRequired + ? ProfileLocalization.CourseCalendarSync.Button.reconnect + : ProfileLocalization.CourseCalendarSync.Button.changeSyncOptions, + action: { + screenDimmed = true + withAnimation(.bouncy(duration: 0.3)) { + if viewModel.reconnectRequired { + viewModel.showCalendaAccessDenided = true + } else { + viewModel.openChangeSyncView = true + } + } + }, + color: viewModel.reconnectRequired + ? Theme.Colors.accentColor + : Theme.Colors.background, + textColor: viewModel.reconnectRequired + ? Theme.Colors.styledButtonText + : Theme.Colors.accentColor, + borderColor: viewModel.reconnectRequired + ? .clear + : Theme.Colors.accentColor + ) + .padding(.horizontal, 24) + if !viewModel.reconnectRequired { + optionTitle(ProfileLocalization.CoursesToSync.title) + .padding(.top, 24) + coursesToSync + .padding(.bottom, 24) + } + relativeDatesToggle + } + .padding(.horizontal, isHorizontal ? 48 : 0) + .frameLimit(width: proxy.size.width) + } + .roundedBackground(Theme.Colors.background) + .ignoresSafeArea(.all, edges: .bottom) + } + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + + // Error Alert if needed + if viewModel.showError { + ErrorAlertView(errorMessage: $viewModel.errorMessage) + } + if screenDimmed { + Color.black.opacity(0.3) + .ignoresSafeArea() + .onTapGesture { + viewModel.openChangeSyncView = false + viewModel.showCalendaAccessDenided = false + screenDimmed = false + } + } + if viewModel.openChangeSyncView { + NewCalendarView( + title: .changeSyncOptions, + viewModel: viewModel, + beginSyncingTapped: {}, + onCloseTapped: { + viewModel.openChangeSyncView = false + screenDimmed = false + } + ) + .transition(.move(edge: .bottom)) + .frame(alignment: .center) + } else if viewModel.showCalendaAccessDenided { + CalendarDialogView( + type: .calendarAccess, + action: { + viewModel.showCalendaAccessDenided = false + screenDimmed = false + viewModel.openAppSettings() + }, + onCloseTapped: { + viewModel.showCalendaAccessDenided = false + screenDimmed = false + } + ) + .transition(.move(edge: .bottom)) + .frame(alignment: .center) + } + + } + .ignoresSafeArea(.all, edges: .horizontal) + } + } + + // MARK: - Options Title + + private func optionTitle(_ text: String) -> some View { + Text(text) + .multilineTextAlignment(.leading) + .font(Theme.Fonts.labelLarge) + .foregroundStyle(Theme.Colors.textPrimary) + .padding(.horizontal, 24) + .frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .leading + ) + } + + // MARK: - Courses to Sync + @ViewBuilder + private var coursesToSync: some View { + + VStack(alignment: .leading, spacing: 27) { + Button(action: { + // viewModel.trackProfileVideoSettingsClicked() + viewModel.router.showCoursesToSync() + }, + label: { + HStack { + Text( + String( + format: ProfileLocalization.CoursesToSync.syncingCourses( + viewModel.coursesForSync.count + ) + ) + ) + .font(Theme.Fonts.titleMedium) + Spacer() + Image(systemName: "chevron.right") + } + }) + .accessibilityIdentifier("courses_to_sync_cell") + + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.settingsVideo) + .cardStyle( + bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear + ) + } + + @ViewBuilder + private var relativeDatesToggle: some View { + Divider() + .padding(.horizontal, 24) + + optionTitle(ProfileLocalization.Options.title) + .padding(.vertical, 16) + ToggleWithDescriptionView( + text: ProfileLocalization.Options.useRelativeDates, + description: ProfileLocalization.Options.showRelativeDates, + toggle: $viewModel.reconnectRequired + ) + .padding(.horizontal, 24) + } +} + +#if DEBUG +struct SyncCalendarOptionsView_Previews: PreviewProvider { + static var previews: some View { + let vm = DatesAndCalendarViewModel( + router: ProfileRouterMock() + ) + SyncCalendarOptionsView(viewModel: vm) + .loadFonts() + } +} +#endif diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index b643845bd..5b2a04c77 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -141,7 +141,7 @@ public struct ProfileView: View { } } } - + // MARK: - Profile Info @ViewBuilder private var profileInfo: some View { diff --git a/Profile/Profile/Presentation/ProfileRouter.swift b/Profile/Profile/Presentation/ProfileRouter.swift index 624a05e21..dca085668 100644 --- a/Profile/Profile/Presentation/ProfileRouter.swift +++ b/Profile/Profile/Presentation/ProfileRouter.swift @@ -24,6 +24,12 @@ public protocol ProfileRouter: BaseRouter { func showManageAccount() + func showDatesAndCalendar() + + func showSyncCalendarOptions() + + func showCoursesToSync() + func showVideoQualityView(viewModel: SettingsViewModel) func showVideoDownloadQualityView( @@ -52,6 +58,12 @@ public class ProfileRouterMock: BaseRouterMock, ProfileRouter { public func showVideoSettings() {} + public func showDatesAndCalendar() {} + + public func showSyncCalendarOptions() {} + + public func showCoursesToSync() {} + public func showManageAccount() {} public func showVideoQualityView(viewModel: SettingsViewModel) {} diff --git a/Profile/Profile/Presentation/Settings/SettingsView.swift b/Profile/Profile/Presentation/Settings/SettingsView.swift index 047e238ad..590afa784 100644 --- a/Profile/Profile/Presentation/Settings/SettingsView.swift +++ b/Profile/Profile/Presentation/Settings/SettingsView.swift @@ -67,6 +67,7 @@ public struct SettingsView: View { } else { manageAccount settings + datesAndCalendar ProfileSupportInfoView(viewModel: viewModel) logOutButton } @@ -108,6 +109,34 @@ public struct SettingsView: View { .ignoresSafeArea(.all, edges: .horizontal) } + // MARK: - Dates & Calendar + + @ViewBuilder + private var datesAndCalendar: some View { + + VStack(alignment: .leading, spacing: 27) { + Button(action: { +// viewModel.trackProfileVideoSettingsClicked() + viewModel.router.showDatesAndCalendar() + }, label: { + HStack { + Text("Dates & Calendar") // TODO: add ProfileLocalization... + .font(Theme.Fonts.titleMedium) + Spacer() + Image(systemName: "chevron.right") + } + }) + .accessibilityIdentifier("dates_and_calendar_cell") + + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.settingsVideo) + .cardStyle( + bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear + ) + } + // MARK: - Manage Account @ViewBuilder private var manageAccount: some View { diff --git a/Profile/Profile/SwiftGen/Strings.swift b/Profile/Profile/SwiftGen/Strings.swift index 5c26b5557..0cf331373 100644 --- a/Profile/Profile/SwiftGen/Strings.swift +++ b/Profile/Profile/SwiftGen/Strings.swift @@ -53,6 +53,120 @@ public enum ProfileLocalization { public static let title = ProfileLocalization.tr("Localizable", "TITLE", fallback: "Profile") /// Year of birth: public static let yearOfBirth = ProfileLocalization.tr("Localizable", "YEAR_OF_BIRTH", fallback: "Year of birth:") + public enum AssignmentStatus { + /// Sync Failed + public static let failed = ProfileLocalization.tr("Localizable", "ASSIGNMENT_STATUS.FAILED", fallback: "Sync Failed") + /// Offline + public static let offline = ProfileLocalization.tr("Localizable", "ASSIGNMENT_STATUS.OFFLINE", fallback: "Offline") + /// Synced + public static let synced = ProfileLocalization.tr("Localizable", "ASSIGNMENT_STATUS.SYNCED", fallback: "Synced") + } + public enum Calendar { + /// Account + public static let account = ProfileLocalization.tr("Localizable", "CALENDAR.ACCOUNT", fallback: "Account") + /// Begin Syncing + public static let beginSyncing = ProfileLocalization.tr("Localizable", "CALENDAR.BEGIN_SYNCING", fallback: "Begin Syncing") + /// Calendar Name + public static let calendarName = ProfileLocalization.tr("Localizable", "CALENDAR.CALENDAR_NAME", fallback: "Calendar Name") + /// Cancel + public static let cancel = ProfileLocalization.tr("Localizable", "CALENDAR.CANCEL", fallback: "Cancel") + /// Change Sync Options + public static let changeSyncOptions = ProfileLocalization.tr("Localizable", "CALENDAR.CHANGE_SYNC_OPTIONS", fallback: "Change Sync Options") + /// Color + public static let color = ProfileLocalization.tr("Localizable", "CALENDAR.COLOR", fallback: "Color") + /// %@ Course Dates + public static func courseDates(_ p1: Any) -> String { + return ProfileLocalization.tr("Localizable", "CALENDAR.COURSE_DATES", String(describing: p1), fallback: "%@ Course Dates") + } + /// New Calendar + public static let newCalendar = ProfileLocalization.tr("Localizable", "CALENDAR.NEW_CALENDAR", fallback: "New Calendar") + /// Upcoming assignments for active courses will appear on this calendar + public static let upcomingAssignments = ProfileLocalization.tr("Localizable", "CALENDAR.UPCOMING_ASSIGNMENTS", fallback: "Upcoming assignments for active courses will appear on this calendar") + public enum Dropdown { + /// iCloud + public static let icloud = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN.ICLOUD", fallback: "iCloud") + /// Local + public static let local = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN.LOCAL", fallback: "Local") + } + public enum DropdownColor { + /// Accent + public static let accent = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.ACCENT", fallback: "Accent") + /// Blue + public static let blue = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.BLUE", fallback: "Blue") + /// Brown + public static let brown = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.BROWN", fallback: "Brown") + /// Green + public static let green = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.GREEN", fallback: "Green") + /// Orange + public static let orange = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.ORANGE", fallback: "Orange") + /// Purple + public static let purple = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.PURPLE", fallback: "Purple") + /// Red + public static let red = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.RED", fallback: "Red") + /// Yellow + public static let yellow = ProfileLocalization.tr("Localizable", "CALENDAR.DROPDOWN_COLOR.YELLOW", fallback: "Yellow") + } + } + public enum CalendarDialog { + /// Calendar Access + public static let calendarAccess = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.CALENDAR_ACCESS", fallback: "Calendar Access") + /// To show upcoming assignments and course milestones on your calendar, we need permission to access your calendar. + public static let calendarAccessDescription = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.CALENDAR_ACCESS_DESCRIPTION", fallback: "To show upcoming assignments and course milestones on your calendar, we need permission to access your calendar.") + /// Cancel + public static let cancel = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.CANCEL", fallback: "Cancel") + /// Change Sync Options + public static let disableCalendarSync = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC", fallback: "Change Sync Options") + /// Disabling calendar sync will delete the calendar “My Assignments.” You can turn calendar sync back on at any time. + public static let disableCalendarSyncDescription = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC_DESCRIPTION", fallback: "Disabling calendar sync will delete the calendar “My Assignments.” You can turn calendar sync back on at any time.") + /// Disable Syncing + public static let disableSyncing = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.DISABLE_SYNCING", fallback: "Disable Syncing") + /// Grant Calendar Access + public static let grantCalendarAccess = ProfileLocalization.tr("Localizable", "CALENDAR_DIALOG.GRANT_CALENDAR_ACCESS", fallback: "Grant Calendar Access") + } + public enum CalendarSync { + /// Set Up Calendar Sync + public static let button = ProfileLocalization.tr("Localizable", "CALENDAR_SYNC.BUTTON", fallback: "Set Up Calendar Sync") + /// Set up calendar sync to show your upcoming assignments and course milestones on your calendar. New assignments and shifted course dates will sync automatically + public static let description = ProfileLocalization.tr("Localizable", "CALENDAR_SYNC.DESCRIPTION", fallback: "Set up calendar sync to show your upcoming assignments and course milestones on your calendar. New assignments and shifted course dates will sync automatically") + /// Calendar Sync + public static let title = ProfileLocalization.tr("Localizable", "CALENDAR_SYNC.TITLE", fallback: "Calendar Sync") + } + public enum CoursesToSync { + /// Disabling sync for a course will remove all events connected to the course from your synced calendar. + public static let description = ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.DESCRIPTION", fallback: "Disabling sync for a course will remove all events connected to the course from your synced calendar.") + /// Hide Inactive Courses + public static let hideInactiveCourses = ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.HIDE_INACTIVE_COURSES", fallback: "Hide Inactive Courses") + /// Automatically remove events from courses you haven’t viewed in the last month + public static let hideInactiveCoursesDescription = ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.HIDE_INACTIVE_COURSES_DESCRIPTION", fallback: "Automatically remove events from courses you haven’t viewed in the last month") + /// Inactive + public static let inactive = ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.INACTIVE", fallback: "Inactive") + /// Syncing %d Courses + public static func syncingCourses(_ p1: Int) -> String { + return ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.SYNCING_COURSES", p1, fallback: "Syncing %d Courses") + } + /// Courses to Sync + public static let title = ProfileLocalization.tr("Localizable", "COURSES_TO_SYNC.TITLE", fallback: "Courses to Sync") + } + public enum CourseCalendarSync { + /// Course Calendar Sync + public static let title = ProfileLocalization.tr("Localizable", "COURSE_CALENDAR_SYNC.TITLE", fallback: "Course Calendar Sync") + public enum Button { + /// Change Sync Options + public static let changeSyncOptions = ProfileLocalization.tr("Localizable", "COURSE_CALENDAR_SYNC.BUTTON.CHANGE_SYNC_OPTIONS", fallback: "Change Sync Options") + /// Reconnect Calendar + public static let reconnect = ProfileLocalization.tr("Localizable", "COURSE_CALENDAR_SYNC.BUTTON.RECONNECT", fallback: "Reconnect Calendar") + } + public enum Description { + /// Please reconnect your calendar to resume syncing + public static let reconnectRequired = ProfileLocalization.tr("Localizable", "COURSE_CALENDAR_SYNC.DESCRIPTION.RECONNECT_REQUIRED", fallback: "Please reconnect your calendar to resume syncing") + /// Currently syncing events to your calendar + public static let syncing = ProfileLocalization.tr("Localizable", "COURSE_CALENDAR_SYNC.DESCRIPTION.SYNCING", fallback: "Currently syncing events to your calendar") + } + } + public enum DatesAndCalendar { + /// Dates & Calendar + public static let title = ProfileLocalization.tr("Localizable", "DATES_AND_CALENDAR.TITLE", fallback: "Dates & Calendar") + } public enum DeleteAccount { /// Are you sure you want to public static let areYouSure = ProfileLocalization.tr("Localizable", "DELETE_ACCOUNT.ARE_YOU_SURE", fallback: "Are you sure you want to ") @@ -79,6 +193,10 @@ public enum ProfileLocalization { /// Warning! public static let title = ProfileLocalization.tr("Localizable", "DELETE_ALERT.TITLE", fallback: "Warning!") } + public enum DropDownPicker { + /// Select + public static let select = ProfileLocalization.tr("Localizable", "DROP_DOWN_PICKER.SELECT", fallback: "Select") + } public enum Edit { /// Delete Account public static let deleteAccount = ProfileLocalization.tr("Localizable", "EDIT.DELETE_ACCOUNT", fallback: "Delete Account") @@ -117,6 +235,14 @@ public enum ProfileLocalization { /// Comfirm log out public static let title = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TITLE", fallback: "Comfirm log out") } + public enum Options { + /// Show relative dates like “Tomorrow” and “Yesterday” + public static let showRelativeDates = ProfileLocalization.tr("Localizable", "OPTIONS.SHOW_RELATIVE_DATES", fallback: "Show relative dates like “Tomorrow” and “Yesterday”") + /// Options + public static let title = ProfileLocalization.tr("Localizable", "OPTIONS.TITLE", fallback: "Options") + /// Use relative dates + public static let useRelativeDates = ProfileLocalization.tr("Localizable", "OPTIONS.USE_RELATIVE_DATES", fallback: "Use relative dates") + } public enum Settings { /// Lower data usage public static let quality360Description = ProfileLocalization.tr("Localizable", "SETTINGS.QUALITY_360_DESCRIPTION", fallback: "Lower data usage") @@ -151,6 +277,12 @@ public enum ProfileLocalization { /// Wi-fi only download public static let wifiTitle = ProfileLocalization.tr("Localizable", "SETTINGS.WIFI_TITLE", fallback: "Wi-fi only download") } + public enum SyncSelector { + /// Not Synced + public static let notSynced = ProfileLocalization.tr("Localizable", "SYNC_SELECTOR.NOT_SYNCED", fallback: "Not Synced") + /// Synced + public static let synced = ProfileLocalization.tr("Localizable", "SYNC_SELECTOR.SYNCED", fallback: "Synced") + } public enum UnsavedDataAlert { /// Changes you have made will be discarded. public static let text = ProfileLocalization.tr("Localizable", "UNSAVED_DATA_ALERT.TEXT", fallback: "Changes you have made will be discarded.") diff --git a/Profile/Profile/en.lproj/Localizable.strings b/Profile/Profile/en.lproj/Localizable.strings index be69b026d..9b6872159 100644 --- a/Profile/Profile/en.lproj/Localizable.strings +++ b/Profile/Profile/en.lproj/Localizable.strings @@ -82,3 +82,68 @@ "SETTINGS.TAP_TO_INSTALL" = "Tap to install required app update"; "ERROR.CANNOT_SEND_EMAIL" = "Cannot send email. It seems your email client is not set up."; + +"CALENDAR.NEW_CALENDAR" = "New Calendar"; +"CALENDAR.CHANGE_SYNC_OPTIONS" = "Change Sync Options"; +"CALENDAR.ACCOUNT" = "Account"; +"CALENDAR.CALENDAR_NAME" = "Calendar Name"; +"CALENDAR.COLOR" = "Color"; +"CALENDAR.UPCOMING_ASSIGNMENTS" = "Upcoming assignments for active courses will appear on this calendar"; +"CALENDAR.CANCEL" = "Cancel"; +"CALENDAR.BEGIN_SYNCING" = "Begin Syncing"; + +"ASSIGNMENT_STATUS.SYNCED" = "Synced"; +"ASSIGNMENT_STATUS.FAILED" = "Sync Failed"; +"ASSIGNMENT_STATUS.OFFLINE" = "Offline"; + +"CALENDAR_DIALOG.CALENDAR_ACCESS" = "Calendar Access"; +"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC" = "Change Sync Options"; +"CALENDAR_DIALOG.CALENDAR_ACCESS_DESCRIPTION" = "To show upcoming assignments and course milestones on your calendar, we need permission to access your calendar."; +"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC_DESCRIPTION" = "Disabling calendar sync will delete the calendar “My Assignments.” You can turn calendar sync back on at any time."; +"CALENDAR_DIALOG.GRANT_CALENDAR_ACCESS" = "Grant Calendar Access"; +"CALENDAR_DIALOG.DISABLE_SYNCING" = "Disable Syncing"; +"CALENDAR_DIALOG.CANCEL" = "Cancel"; + +"DATES_AND_CALENDAR.TITLE" = "Dates & Calendar"; +"CALENDAR_SYNC.TITLE" = "Calendar Sync"; +"CALENDAR_SYNC.DESCRIPTION" = "Set up calendar sync to show your upcoming assignments and course milestones on your calendar. New assignments and shifted course dates will sync automatically"; +"CALENDAR_SYNC.BUTTON" = "Set Up Calendar Sync"; +"OPTIONS.TITLE" = "Options"; +"OPTIONS.USE_RELATIVE_DATES" = "Use relative dates"; +"OPTIONS.SHOW_RELATIVE_DATES" = "Show relative dates like “Tomorrow” and “Yesterday”"; + +"DATES_AND_CALENDAR.TITLE" = "Dates & Calendar"; +"COURSE_CALENDAR_SYNC.TITLE" = "Course Calendar Sync"; +"COURSE_CALENDAR_SYNC.DESCRIPTION.RECONNECT_REQUIRED" = "Please reconnect your calendar to resume syncing"; +"COURSE_CALENDAR_SYNC.DESCRIPTION.SYNCING" = "Currently syncing events to your calendar"; +"COURSE_CALENDAR_SYNC.BUTTON.RECONNECT" = "Reconnect Calendar"; +"COURSE_CALENDAR_SYNC.BUTTON.CHANGE_SYNC_OPTIONS" = "Change Sync Options"; +"COURSES_TO_SYNC.SYNCING_COURSES" = "Syncing %d Courses"; +"OPTIONS.TITLE" = "Options"; +"OPTIONS.USE_RELATIVE_DATES" = "Use relative dates"; +"OPTIONS.SHOW_RELATIVE_DATES" = "Show relative dates like “Tomorrow” and “Yesterday”"; + +"COURSES_TO_SYNC.TITLE" = "Courses to Sync"; +"COURSES_TO_SYNC.DESCRIPTION" = "Disabling sync for a course will remove all events connected to the course from your synced calendar."; +"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES" = "Hide Inactive Courses"; +"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES_DESCRIPTION" = "Automatically remove events from courses you haven’t viewed in the last month"; +"COURSES_TO_SYNC.INACTIVE" = "Inactive"; + +"CALENDAR.DROPDOWN.ICLOUD" = "iCloud"; +"CALENDAR.DROPDOWN.LOCAL" = "Local"; + +"CALENDAR.DROPDOWN_COLOR.ACCENT" = "Accent"; +"CALENDAR.DROPDOWN_COLOR.RED" = "Red"; +"CALENDAR.DROPDOWN_COLOR.ORANGE" = "Orange"; +"CALENDAR.DROPDOWN_COLOR.YELLOW" = "Yellow"; +"CALENDAR.DROPDOWN_COLOR.GREEN" = "Green"; +"CALENDAR.DROPDOWN_COLOR.BLUE" = "Blue"; +"CALENDAR.DROPDOWN_COLOR.PURPLE" = "Purple"; +"CALENDAR.DROPDOWN_COLOR.BROWN" = "Brown"; + +"CALENDAR.COURSE_DATES" = "%@ Course Dates"; + +"DROP_DOWN_PICKER.SELECT" = "Select"; + +"SYNC_SELECTOR.SYNCED" = "Synced"; +"SYNC_SELECTOR.NOT_SYNCED" = "Not Synced"; diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings index dbc3c5379..a8590f765 100644 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ b/Profile/Profile/uk.lproj/Localizable.strings @@ -80,3 +80,66 @@ "SETTINGS.TAP_TO_INSTALL" = "Клацніть, щоб встановити обов'язкове оновлення програми"; "ERROR.CANNOT_SEND_EMAIL" = "Cannot send email. It seems your email client is not set up."; + +"CALENDAR.NEW_CALENDAR" = "Новий календар"; +"CALENDAR.CHANGE_SYNC_OPTIONS" = "Змінити параметри синхронізації"; +"CALENDAR.ACCOUNT" = "Обліковий запис"; +"CALENDAR.CALENDAR_NAME" = "Назва календаря"; +"CALENDAR.COLOR" = "Колір"; +"CALENDAR.UPCOMING_ASSIGNMENTS" = "Майбутні завдання для активних курсів з’являться у цьому календарі"; +"CALENDAR.CANCEL" = "Скасувати"; +"CALENDAR.BEGIN_SYNCING" = "Почати синхронізацію"; + +"ASSIGNMENT_STATUS.SYNCED" = "Синхронізовано"; +"ASSIGNMENT_STATUS.FAILED" = "Синхронізація не вдалася"; +"ASSIGNMENT_STATUS.OFFLINE" = "Офлайн"; + +"CALENDAR_DIALOG.CALENDAR_ACCESS" = "Доступ до календаря"; +"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC" = "Змінити параметри синхронізації"; +"CALENDAR_DIALOG.CALENDAR_ACCESS_DESCRIPTION" = "Щоб показати майбутні завдання та віхи курсу у вашому календарі, нам потрібен дозвіл на доступ до вашого календаря."; +"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC_DESCRIPTION" = "Вимкнення синхронізації календаря видалить календар “Мої завдання”. Ви можете знову увімкнути синхронізацію календаря в будь-який час."; +"CALENDAR_DIALOG.GRANT_CALENDAR_ACCESS" = "Надати доступ до календаря"; +"CALENDAR_DIALOG.DISABLE_SYNCING" = "Вимкнути синхронізацію"; +"CALENDAR_DIALOG.CANCEL" = "Скасувати"; + +"DATES_AND_CALENDAR.TITLE" = "Дати та календар"; +"CALENDAR_SYNC.TITLE" = "Синхронізація календаря"; +"CALENDAR_SYNC.DESCRIPTION" = "Налаштуйте синхронізацію календаря, щоб показувати майбутні завдання та віхи курсу у вашому календарі. Нові завдання та змінені дати курсів будуть синхронізуватися автоматично"; +"CALENDAR_SYNC.BUTTON" = "Налаштувати синхронізацію календаря"; +"OPTIONS.TITLE" = "Опції"; +"OPTIONS.USE_RELATIVE_DATES" = "Використовувати відносні дати"; +"OPTIONS.SHOW_RELATIVE_DATES" = "Показувати відносні дати, такі як “Завтра” і “Вчора”"; + +"DATES_AND_CALENDAR.TITLE" = "Дати та календарі"; +"CALENDAR_SYNC.TITLE" = "Синхронізація календаря"; +"COURSE_CALENDAR_SYNC.TITLE" = "Синхронізація календаря курсу"; +"COURSE_CALENDAR_SYNC.DESCRIPTION.RECONNECT_REQUIRED" = "Будь ласка, повторно підключіть свій календар для відновлення синхронізації"; +"COURSE_CALENDAR_SYNC.DESCRIPTION.SYNCING" = "В даний час події синхронізуються з вашим календарем"; +"COURSE_CALENDAR_SYNC.BUTTON.RECONNECT" = "Повторно підключити календар"; +"COURSE_CALENDAR_SYNC.BUTTON.CHANGE_SYNC_OPTIONS" = "Змінити параметри синхронізації"; +"COURSES_TO_SYNC.TITLE" = "Синхронізація %d курсів"; +"OPTIONS.TITLE" = "Опції"; +"OPTIONS.USE_RELATIVE_DATES" = "Використовувати відносні дати"; +"OPTIONS.SHOW_RELATIVE_DATES" = "Показувати відносні дати, такі як “Завтра” і “Вчора”"; + +"COURSES_TO_SYNC.TITLE" = "Курси для синхронізації"; +"COURSES_TO_SYNC.DESCRIPTION" = "Вимкнення синхронізації для курсу видалить усі події, пов’язані з курсом, із вашого синхронізованого календаря."; +"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES" = "Приховати неактивні курси"; +"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES_DESCRIPTION" = "Автоматично видаляйте події з курсів, які ви не переглядали протягом останнього місяця"; +"COURSES_TO_SYNC.INACTIVE" = "Неактивний"; + +"CALENDAR.DROPDOWN.ICLOUD" = "iCloud"; +"CALENDAR.DROPDOWN.LOCAL" = "Локальний"; + +"CALENDAR.DROPDOWN_COLOR.ACCENT" = "Акцентний"; +"CALENDAR.DROPDOWN_COLOR.RED" = "Червоний"; +"CALENDAR.DROPDOWN_COLOR.ORANGE" = "Помаранчевий"; +"CALENDAR.DROPDOWN_COLOR.YELLOW" = "Жовтий"; +"CALENDAR.DROPDOWN_COLOR.GREEN" = "Зелений"; +"CALENDAR.DROPDOWN_COLOR.BLUE" = "Синій"; +"CALENDAR.DROPDOWN_COLOR.PURPLE" = "Фіолетовий"; +"CALENDAR.DROPDOWN_COLOR.BROWN" = "Коричневий"; + +"CALENDAR.COURSE_DATES" = "%@ Дати курсу"; + +"DROP_DOWN_PICKER.SELECT" = "Оберіть"; diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 8f927c355..1a3cd757b 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -3057,6 +3057,24 @@ open class ProfileRouterMock: ProfileRouter, Mock { perform?() } + open func showDatesAndCalendar() { + addInvocation(.m_showDatesAndCalendar) + let perform = methodPerformValue(.m_showDatesAndCalendar) as? () -> Void + perform?() + } + + open func showSyncCalendarOptions() { + addInvocation(.m_showSyncCalendarOptions) + let perform = methodPerformValue(.m_showSyncCalendarOptions) as? () -> Void + perform?() + } + + open func showCoursesToSync() { + addInvocation(.m_showCoursesToSync) + let perform = methodPerformValue(.m_showCoursesToSync) as? () -> Void + perform?() + } + open func showVideoQualityView(viewModel: SettingsViewModel) { addInvocation(.m_showVideoQualityView__viewModel_viewModel(Parameter.value(`viewModel`))) let perform = methodPerformValue(.m_showVideoQualityView__viewModel_viewModel(Parameter.value(`viewModel`))) as? (SettingsViewModel) -> Void @@ -3177,6 +3195,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { case m_showSettings case m_showVideoSettings case m_showManageAccount + case m_showDatesAndCalendar + case m_showSyncCalendarOptions + case m_showCoursesToSync case m_showVideoQualityView__viewModel_viewModel(Parameter) case m_showVideoDownloadQualityView__downloadQuality_downloadQualitydidSelect_didSelectanalytics_analytics(Parameter, Parameter<((DownloadQuality) -> Void)?>, Parameter) case m_showDeleteProfileView @@ -3212,6 +3233,12 @@ open class ProfileRouterMock: ProfileRouter, Mock { case (.m_showManageAccount, .m_showManageAccount): return .match + case (.m_showDatesAndCalendar, .m_showDatesAndCalendar): return .match + + case (.m_showSyncCalendarOptions, .m_showSyncCalendarOptions): return .match + + case (.m_showCoursesToSync, .m_showCoursesToSync): return .match + case (.m_showVideoQualityView__viewModel_viewModel(let lhsViewmodel), .m_showVideoQualityView__viewModel_viewModel(let rhsViewmodel)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsViewmodel, rhs: rhsViewmodel, with: matcher), lhsViewmodel, rhsViewmodel, "viewModel")) @@ -3324,6 +3351,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showSettings: return 0 case .m_showVideoSettings: return 0 case .m_showManageAccount: return 0 + case .m_showDatesAndCalendar: return 0 + case .m_showSyncCalendarOptions: return 0 + case .m_showCoursesToSync: return 0 case let .m_showVideoQualityView__viewModel_viewModel(p0): return p0.intValue case let .m_showVideoDownloadQualityView__downloadQuality_downloadQualitydidSelect_didSelectanalytics_analytics(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case .m_showDeleteProfileView: return 0 @@ -3351,6 +3381,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showSettings: return ".showSettings()" case .m_showVideoSettings: return ".showVideoSettings()" case .m_showManageAccount: return ".showManageAccount()" + case .m_showDatesAndCalendar: return ".showDatesAndCalendar()" + case .m_showSyncCalendarOptions: return ".showSyncCalendarOptions()" + case .m_showCoursesToSync: return ".showCoursesToSync()" case .m_showVideoQualityView__viewModel_viewModel: return ".showVideoQualityView(viewModel:)" case .m_showVideoDownloadQualityView__downloadQuality_downloadQualitydidSelect_didSelectanalytics_analytics: return ".showVideoDownloadQualityView(downloadQuality:didSelect:analytics:)" case .m_showDeleteProfileView: return ".showDeleteProfileView()" @@ -3392,6 +3425,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func showSettings() -> Verify { return Verify(method: .m_showSettings)} public static func showVideoSettings() -> Verify { return Verify(method: .m_showVideoSettings)} public static func showManageAccount() -> Verify { return Verify(method: .m_showManageAccount)} + public static func showDatesAndCalendar() -> Verify { return Verify(method: .m_showDatesAndCalendar)} + public static func showSyncCalendarOptions() -> Verify { return Verify(method: .m_showSyncCalendarOptions)} + public static func showCoursesToSync() -> Verify { return Verify(method: .m_showCoursesToSync)} public static func showVideoQualityView(viewModel: Parameter) -> Verify { return Verify(method: .m_showVideoQualityView__viewModel_viewModel(`viewModel`))} public static func showVideoDownloadQualityView(downloadQuality: Parameter, didSelect: Parameter<((DownloadQuality) -> Void)?>, analytics: Parameter) -> Verify { return Verify(method: .m_showVideoDownloadQualityView__downloadQuality_downloadQualitydidSelect_didSelectanalytics_analytics(`downloadQuality`, `didSelect`, `analytics`))} public static func showDeleteProfileView() -> Verify { return Verify(method: .m_showDeleteProfileView)} @@ -3429,6 +3465,15 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func showManageAccount(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showManageAccount, performs: perform) } + public static func showDatesAndCalendar(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showDatesAndCalendar, performs: perform) + } + public static func showSyncCalendarOptions(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showSyncCalendarOptions, performs: perform) + } + public static func showCoursesToSync(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showCoursesToSync, performs: perform) + } public static func showVideoQualityView(viewModel: Parameter, perform: @escaping (SettingsViewModel) -> Void) -> Perform { return Perform(method: .m_showVideoQualityView__viewModel_viewModel(`viewModel`), performs: perform) }