diff --git a/Models/Schedule/Event.swift b/Models/Schedule/Event.swift index f0f1082..7737eeb 100644 --- a/Models/Schedule/Event.swift +++ b/Models/Schedule/Event.swift @@ -35,5 +35,26 @@ public struct Event: Codable { return dateFormatter.string(from: date) } + + public var timeParsed: String { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = TimeZone(identifier: "Europe/Riga") + + if let timeFromDate = date { + dateFormatter.dateFormat = "HH:mm" + dateFormatter.timeZone = TimeZone.current + return dateFormatter.string(from: timeFromDate) + } + + return time + } + + public var date: Date? { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = TimeZone(identifier: "Europe/Riga") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm" + + return dateFormatter.date(from: datetime) + } } diff --git a/ORTUS.xcodeproj/project.pbxproj b/ORTUS.xcodeproj/project.pbxproj index 3ae1741..010531d 100644 --- a/ORTUS.xcodeproj/project.pbxproj +++ b/ORTUS.xcodeproj/project.pbxproj @@ -10,6 +10,21 @@ FA0927D42347E3B000D32827 /* NotificationsApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0927D32347E3B000D32827 /* NotificationsApi.swift */; }; FA0A75A124D856C30086E878 /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0A75A024D856C30086E878 /* Overlay.swift */; }; FA0BEFDB2443293E00A10A0B /* ButtonsStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0BEFDA2443293E00A10A0B /* ButtonsStyle.swift */; }; + FA181B2F24F822C500402667 /* EventDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA181B2E24F822C500402667 /* EventDescriptionView.swift */; }; + FA181B3124F822F300402667 /* EventDescriptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA181B3024F822F300402667 /* EventDescriptionViewController.swift */; }; + FA1AC40424EDA3B7009C4E31 /* MarkModuleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC3FF24EDA3B7009C4E31 /* MarkModuleBuilder.swift */; }; + FA1AC40524EDA3B7009C4E31 /* MarkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40024EDA3B7009C4E31 /* MarkRouter.swift */; }; + FA1AC40624EDA3B7009C4E31 /* MarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40124EDA3B7009C4E31 /* MarkViewController.swift */; }; + FA1AC40724EDA3B7009C4E31 /* MarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40224EDA3B7009C4E31 /* MarkView.swift */; }; + FA1AC40824EDA3B7009C4E31 /* MarkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40324EDA3B7009C4E31 /* MarkViewModel.swift */; }; + FA1AC40A24EDA3E0009C4E31 /* MarkRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40924EDA3E0009C4E31 /* MarkRoute.swift */; }; + FA1AC40E24EDA904009C4E31 /* ORTUSSelectionNoneTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40D24EDA904009C4E31 /* ORTUSSelectionNoneTableViewCell.swift */; }; + FA1AC41024EDA985009C4E31 /* TextDescriptionComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC40F24EDA985009C4E31 /* TextDescriptionComponent.swift */; }; + FA1AC41224EDA991009C4E31 /* TextDescriptionComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC41124EDA991009C4E31 /* TextDescriptionComponentView.swift */; }; + FA1AC41424EDAB59009C4E31 /* MarkComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC41324EDAB59009C4E31 /* MarkComponent.swift */; }; + FA1AC41624EDAB65009C4E31 /* MarkComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC41524EDAB65009C4E31 /* MarkComponentView.swift */; }; + FA1AC41924EEE4D4009C4E31 /* SemesterComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC41824EEE4D4009C4E31 /* SemesterComponent.swift */; }; + FA1AC41B24EEE4E3009C4E31 /* SemesterComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1AC41A24EEE4E3009C4E31 /* SemesterComponentView.swift */; }; FA1DEF872416DAF300704B6E /* LectureComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1DEF862416DAF300704B6E /* LectureComponent.swift */; }; FA1DEF892416DAFE00704B6E /* LectureComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1DEF882416DAFE00704B6E /* LectureComponentView.swift */; }; FA1DEF8B2417603100704B6E /* VersionComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1DEF8A2417603100704B6E /* VersionComponent.swift */; }; @@ -153,7 +168,6 @@ FAB74C1123E9F391004C7BC6 /* GradesModuleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C0C23E9F391004C7BC6 /* GradesModuleBuilder.swift */; }; FAB74C1223E9F391004C7BC6 /* GradesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C0D23E9F391004C7BC6 /* GradesRouter.swift */; }; FAB74C1323E9F391004C7BC6 /* GradesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C0E23E9F391004C7BC6 /* GradesViewController.swift */; }; - FAB74C1423E9F391004C7BC6 /* GradesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C0F23E9F391004C7BC6 /* GradesView.swift */; }; FAB74C1523E9F391004C7BC6 /* GradesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C1023E9F391004C7BC6 /* GradesViewModel.swift */; }; FAB74C1823E9F46D004C7BC6 /* TextComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C1723E9F46D004C7BC6 /* TextComponent.swift */; }; FAB74C1A23E9F478004C7BC6 /* TextComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB74C1923E9F478004C7BC6 /* TextComponentView.swift */; }; @@ -335,6 +349,21 @@ FA0927D32347E3B000D32827 /* NotificationsApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsApi.swift; sourceTree = ""; }; FA0A75A024D856C30086E878 /* Overlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overlay.swift; sourceTree = ""; }; FA0BEFDA2443293E00A10A0B /* ButtonsStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsStyle.swift; sourceTree = ""; }; + FA181B2E24F822C500402667 /* EventDescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDescriptionView.swift; sourceTree = ""; }; + FA181B3024F822F300402667 /* EventDescriptionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDescriptionViewController.swift; sourceTree = ""; }; + FA1AC3FF24EDA3B7009C4E31 /* MarkModuleBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkModuleBuilder.swift; sourceTree = ""; }; + FA1AC40024EDA3B7009C4E31 /* MarkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkRouter.swift; sourceTree = ""; }; + FA1AC40124EDA3B7009C4E31 /* MarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkViewController.swift; sourceTree = ""; }; + FA1AC40224EDA3B7009C4E31 /* MarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkView.swift; sourceTree = ""; }; + FA1AC40324EDA3B7009C4E31 /* MarkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkViewModel.swift; sourceTree = ""; }; + FA1AC40924EDA3E0009C4E31 /* MarkRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkRoute.swift; sourceTree = ""; }; + FA1AC40D24EDA904009C4E31 /* ORTUSSelectionNoneTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ORTUSSelectionNoneTableViewCell.swift; sourceTree = ""; }; + FA1AC40F24EDA985009C4E31 /* TextDescriptionComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDescriptionComponent.swift; sourceTree = ""; }; + FA1AC41124EDA991009C4E31 /* TextDescriptionComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDescriptionComponentView.swift; sourceTree = ""; }; + FA1AC41324EDAB59009C4E31 /* MarkComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkComponent.swift; sourceTree = ""; }; + FA1AC41524EDAB65009C4E31 /* MarkComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkComponentView.swift; sourceTree = ""; }; + FA1AC41824EEE4D4009C4E31 /* SemesterComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemesterComponent.swift; sourceTree = ""; }; + FA1AC41A24EEE4E3009C4E31 /* SemesterComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemesterComponentView.swift; sourceTree = ""; }; FA1DEF862416DAF300704B6E /* LectureComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureComponent.swift; sourceTree = ""; }; FA1DEF882416DAFE00704B6E /* LectureComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureComponentView.swift; sourceTree = ""; }; FA1DEF8A2417603100704B6E /* VersionComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionComponent.swift; sourceTree = ""; }; @@ -491,7 +520,6 @@ FAB74C0C23E9F391004C7BC6 /* GradesModuleBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesModuleBuilder.swift; sourceTree = ""; }; FAB74C0D23E9F391004C7BC6 /* GradesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesRouter.swift; sourceTree = ""; }; FAB74C0E23E9F391004C7BC6 /* GradesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesViewController.swift; sourceTree = ""; }; - FAB74C0F23E9F391004C7BC6 /* GradesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesView.swift; sourceTree = ""; }; FAB74C1023E9F391004C7BC6 /* GradesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesViewModel.swift; sourceTree = ""; }; FAB74C1723E9F46D004C7BC6 /* TextComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextComponent.swift; sourceTree = ""; }; FAB74C1923E9F478004C7BC6 /* TextComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextComponentView.swift; sourceTree = ""; }; @@ -670,6 +698,27 @@ path = Notifications; sourceTree = ""; }; + FA181B2D24F822A500402667 /* Schedule Description */ = { + isa = PBXGroup; + children = ( + FA181B2E24F822C500402667 /* EventDescriptionView.swift */, + FA181B3024F822F300402667 /* EventDescriptionViewController.swift */, + ); + path = "Schedule Description"; + sourceTree = ""; + }; + FA1AC3FE24EDA3A1009C4E31 /* Mark */ = { + isa = PBXGroup; + children = ( + FA1AC3FF24EDA3B7009C4E31 /* MarkModuleBuilder.swift */, + FA1AC40224EDA3B7009C4E31 /* MarkView.swift */, + FA1AC40324EDA3B7009C4E31 /* MarkViewModel.swift */, + FA1AC40124EDA3B7009C4E31 /* MarkViewController.swift */, + FA1AC40024EDA3B7009C4E31 /* MarkRouter.swift */, + ); + path = Mark; + sourceTree = ""; + }; FA20623323434DAA007ED9BA /* Courses */ = { isa = PBXGroup; children = ( @@ -699,6 +748,8 @@ children = ( FA2062C623453DEB007ED9BA /* CourseComponent.swift */, FA2062C823453E00007ED9BA /* CourseComponentView.swift */, + FA1AC41824EEE4D4009C4E31 /* SemesterComponent.swift */, + FA1AC41A24EEE4E3009C4E31 /* SemesterComponentView.swift */, ); path = Course; sourceTree = ""; @@ -806,6 +857,7 @@ FA45909224409C0D00BF7E0F /* ScrollTableViewAdapter.swift */, FACACC99249A8F6E0075604F /* ORTUSTableViewAdapter.swift */, FACACC9B249A8FD10075604F /* ORTUSTableViewCell.swift */, + FA1AC40D24EDA904009C4E31 /* ORTUSSelectionNoneTableViewCell.swift */, ); path = Carbon; sourceTree = ""; @@ -1240,8 +1292,8 @@ FAB74C0B23E9F377004C7BC6 /* Grades */ = { isa = PBXGroup; children = ( + FA1AC3FE24EDA3A1009C4E31 /* Mark */, FAB74C0C23E9F391004C7BC6 /* GradesModuleBuilder.swift */, - FAB74C0F23E9F391004C7BC6 /* GradesView.swift */, FAB74C1023E9F391004C7BC6 /* GradesViewModel.swift */, FAB74C0E23E9F391004C7BC6 /* GradesViewController.swift */, FAB74C0D23E9F391004C7BC6 /* GradesRouter.swift */, @@ -1259,6 +1311,8 @@ FA1DEF8A2417603100704B6E /* VersionComponent.swift */, FA1DEF8C2417605400704B6E /* VersionComponentView.swift */, FA89749224BB05670077C64C /* StateComponent.swift */, + FA1AC40F24EDA985009C4E31 /* TextDescriptionComponent.swift */, + FA1AC41124EDA991009C4E31 /* TextDescriptionComponentView.swift */, ); path = Common; sourceTree = ""; @@ -1287,6 +1341,7 @@ isa = PBXGroup; children = ( FAB74C3023E9FAFD004C7BC6 /* GradesRoute.swift */, + FA1AC40924EDA3E0009C4E31 /* MarkRoute.swift */, ); path = Grades; sourceTree = ""; @@ -1314,6 +1369,8 @@ children = ( FAB74C4123EAB1C5004C7BC6 /* GradeComponent.swift */, FAB74C4323EAB1D1004C7BC6 /* GradeComponentView.swift */, + FA1AC41324EDAB59009C4E31 /* MarkComponent.swift */, + FA1AC41524EDAB65009C4E31 /* MarkComponentView.swift */, ); path = Grades; sourceTree = ""; @@ -1439,6 +1496,7 @@ FAF183BF22490FB500CA2492 /* Schedule */ = { isa = PBXGroup; children = ( + FA181B2D24F822A500402667 /* Schedule Description */, FAF183C022490FC600CA2492 /* ScheduleModuleBuilder.swift */, FAF183C322490FC600CA2492 /* ScheduleView.swift */, FAF183C422490FC600CA2492 /* ScheduleViewModel.swift */, @@ -1704,7 +1762,6 @@ files = ( FAB74C3123E9FAFD004C7BC6 /* GradesRoute.swift in Sources */, FA45906E243F30A100BF7E0F /* ContactsViewController.swift in Sources */, - FAB74C1423E9F391004C7BC6 /* GradesView.swift in Sources */, FAD3CA2E2245612C009DA6AE /* UIApplication.swift in Sources */, FABF93A1238BB15E00F78FF1 /* EventLogger.swift in Sources */, FA2062DF234628A6007ED9BA /* NotificationsViewModel.swift in Sources */, @@ -1719,6 +1776,7 @@ FA90FDB822438069005148AC /* UIImage.swift in Sources */, FABF939A2389C7E200F78FF1 /* ScheduleTableViewAdapter.swift in Sources */, FA90FD9722437D50005148AC /* Router.swift in Sources */, + FA1AC40A24EDA3E0009C4E31 /* MarkRoute.swift in Sources */, FA90FDF72243C5BF005148AC /* ArticleComponentView.swift in Sources */, FAD43273243927A0001DA513 /* ContactsApi.swift in Sources */, FA39AEBE23E8CB8300C4A70C /* NotificationComponent.swift in Sources */, @@ -1730,6 +1788,7 @@ FA20623523434DBB007ED9BA /* CoursesApi.swift in Sources */, FAF183C522490FC600CA2492 /* ScheduleModuleBuilder.swift in Sources */, FAD18D5723B1746600C0D89B /* BrowserRouter.swift in Sources */, + FA1AC40E24EDA904009C4E31 /* ORTUSSelectionNoneTableViewCell.swift in Sources */, FAB74C1523E9F391004C7BC6 /* GradesViewModel.swift in Sources */, FAB74C4823EBE43D004C7BC6 /* FormSection.swift in Sources */, FA8714D423E40DD100DB728C /* PinSettingsRouter.swift in Sources */, @@ -1746,10 +1805,13 @@ FAE3AB3D239D100500120F96 /* Assets.swift in Sources */, FAB74C4C23EC48B7004C7BC6 /* FormSwitch.swift in Sources */, FAD3CA2C22455E25009DA6AE /* ArticleTableViewAdapter.swift in Sources */, + FA1AC41924EEE4D4009C4E31 /* SemesterComponent.swift in Sources */, FA90FDC2224380FE005148AC /* SettingsViewModel.swift in Sources */, FA45908D244086CD00BF7E0F /* ContactView.swift in Sources */, FA90FDB422437F80005148AC /* Notification.swift in Sources */, FABBF40123512A4D00D4D593 /* LectureComponentView.swift in Sources */, + FA1AC41224EDA991009C4E31 /* TextDescriptionComponentView.swift in Sources */, + FA1AC40424EDA3B7009C4E31 /* MarkModuleBuilder.swift in Sources */, FAF77B272246A98500197641 /* FormButton.swift in Sources */, FAF77B222246A51800197641 /* OAuth.swift in Sources */, FAB74C5723EC4A50004C7BC6 /* ScheduleSettingsViewController.swift in Sources */, @@ -1776,6 +1838,7 @@ FAF77B292246AC3C00197641 /* Header.swift in Sources */, FA8714D623E40DD100DB728C /* PinSettingsView.swift in Sources */, FA990A63239D6F8600BE9ACA /* LoginOnboardingContentView.swift in Sources */, + FA1AC41624EDAB65009C4E31 /* MarkComponentView.swift in Sources */, FA20623823434DF2007ED9BA /* ScheduleApi.swift in Sources */, FAE5284F244462AB009851C9 /* ContactInfoComponentView.swift in Sources */, FA45907F243F6DF500BF7E0F /* ContactComponent.swift in Sources */, @@ -1784,16 +1847,21 @@ FA327C1C2243FCE40061E604 /* ArticleHeaderComponent.swift in Sources */, FA45906F243F30A100BF7E0F /* ContactsView.swift in Sources */, FAD18D5923B1746600C0D89B /* BrowserView.swift in Sources */, + FA181B3124F822F300402667 /* EventDescriptionViewController.swift in Sources */, FAB74C2A23E9F976004C7BC6 /* IconTextComponent.swift in Sources */, FAB74C2C23E9F996004C7BC6 /* IconTextComponentView.swift in Sources */, + FA1AC40724EDA3B7009C4E31 /* MarkView.swift in Sources */, FACACC9F249A96130075604F /* ORTUSTableView.swift in Sources */, FAB74C5B23EC4B5F004C7BC6 /* ScheduleSettingsRoute.swift in Sources */, + FA1AC41024EDA985009C4E31 /* TextDescriptionComponent.swift in Sources */, FAE3AB36239D0D1700120F96 /* LinearGradient.swift in Sources */, FA39AEDA23E9ED3800C4A70C /* HomeModuleBuilder.swift in Sources */, FAE528512444EE0E009851C9 /* LatviaDateFormatter.swift in Sources */, + FA181B2F24F822C500402667 /* EventDescriptionView.swift in Sources */, FA39AEC023E8CB8F00C4A70C /* NotificationComponentView.swift in Sources */, FA90FE012243CF0E005148AC /* ArticleView.swift in Sources */, FA990A5D239D1F5800BE9ACA /* MainTabBarControllerRoute.swift in Sources */, + FA1AC41B24EEE4E3009C4E31 /* SemesterComponentView.swift in Sources */, FA89749324BB05670077C64C /* StateComponent.swift in Sources */, FAB74C4A23EBE4E4004C7BC6 /* FormLink.swift in Sources */, FA90FD8122437B51005148AC /* MainTabBarController.swift in Sources */, @@ -1823,6 +1891,7 @@ FAF183C822490FC600CA2492 /* ScheduleView.swift in Sources */, FA90FDAE22437F25005148AC /* NewsRouter.swift in Sources */, FAF28BD024D73FCB00627CA3 /* Shortcut.swift in Sources */, + FA1AC40824EDA3B7009C4E31 /* MarkViewModel.swift in Sources */, FA2062C923453E00007ED9BA /* CourseComponentView.swift in Sources */, FA2062DB234628A6007ED9BA /* NotificationsModuleBuilder.swift in Sources */, FA62521A2387F8D2009C3FF7 /* UINavigationController.swift in Sources */, @@ -1874,6 +1943,7 @@ FA90FD68224377A1005148AC /* AppDelegate.swift in Sources */, FA90FDF52243C5B5005148AC /* ArticleComponent.swift in Sources */, FA90FDB122437F25005148AC /* NewsViewModel.swift in Sources */, + FA1AC41424EDAB59009C4E31 /* MarkComponent.swift in Sources */, FA459081243F6E1200BF7E0F /* ContactComponentView.swift in Sources */, FAB74C2423E9F60D004C7BC6 /* SemesterView.swift in Sources */, FAB74C2523E9F60D004C7BC6 /* SemesterViewModel.swift in Sources */, @@ -1881,6 +1951,7 @@ FA45908A244086CD00BF7E0F /* ContactModuleBuilder.swift in Sources */, FA90FDC0224380FE005148AC /* SettingsViewController.swift in Sources */, FA90FDAD22437F25005148AC /* NewsModuleBuilder.swift in Sources */, + FA1AC40524EDA3B7009C4E31 /* MarkRouter.swift in Sources */, FA90FE002243CF0E005148AC /* ArticleViewController.swift in Sources */, FA990A65239D7EB900BE9ACA /* LoginRoute.swift in Sources */, FAB74C4623EAB2EC004C7BC6 /* CircleView.swift in Sources */, @@ -1889,6 +1960,7 @@ FA90FD9222437C9D005148AC /* Styling.swift in Sources */, FA90FDE12243966B005148AC /* API.swift in Sources */, FA90FD8B22437BD0005148AC /* Module.swift in Sources */, + FA1AC40624EDA3B7009C4E31 /* MarkViewController.swift in Sources */, FAB74C2823E9F636004C7BC6 /* SemesterRoute.swift in Sources */, FA90FE082243D1E5005148AC /* ArticleSectionHeader.swift in Sources */, ); @@ -1950,7 +2022,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Schedule/Schedule.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = RL7M4Z58CJ; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Schedule/Info.plist; @@ -1960,7 +2032,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.2.0; + MARKETING_VERSION = 0.3.0; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = me.recouse.ORTUS.Schedule; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1976,7 +2048,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Schedule/Schedule.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = RL7M4Z58CJ; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Schedule/Info.plist; @@ -1986,7 +2058,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.2.0; + MARKETING_VERSION = 0.3.0; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = me.recouse.ORTUS.Schedule; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2180,7 +2252,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ORTUS/ORTUS.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = RL7M4Z58CJ; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -2194,7 +2266,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.2.0; + MARKETING_VERSION = 0.3.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = me.recouse.ORTUS; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2211,7 +2283,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ORTUS/ORTUS.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = RL7M4Z58CJ; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -2225,7 +2297,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.2.0; + MARKETING_VERSION = 0.3.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = me.recouse.ORTUS; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ORTUS/API/APIClient.swift b/ORTUS/API/APIClient.swift index c0e1c6f..d9bed44 100644 --- a/ORTUS/API/APIClient.swift +++ b/ORTUS/API/APIClient.swift @@ -43,6 +43,8 @@ class APIClient { _ responseType: T.Type, route: API ) -> Promise { + Self.showActivityIndicator() + return Promise { fulfill, reject in AF.upload(multipartFormData: { guard let parameters = route.parameters else { return } @@ -61,6 +63,8 @@ class APIClient { $0.append(parameterValue.data(using: .utf8) ?? Data(), withName: key) } }, with: route).responseDecodable { (response: DataResponse) in + Self.hideActivityIndicator() + switch response.result { case .success(let responseObject): fulfill(responseObject) @@ -76,8 +80,12 @@ class APIClient { route: API, decoder: JSONDecoder = JSONDecoder() ) -> Promise { + Self.showActivityIndicator() + return Promise { fulfill, reject in AF.request(route).responseDecodable (decoder: decoder) { (response: DataResponse) in + Self.hideActivityIndicator() + switch response.result { case .success(let responseObject): fulfill(responseObject) @@ -87,4 +95,12 @@ class APIClient { } } } + + private static func showActivityIndicator() { + UIApplication.shared.isNetworkActivityIndicatorVisible = true + } + + private static func hideActivityIndicator() { + UIApplication.shared.isNetworkActivityIndicatorVisible = false + } } diff --git a/ORTUS/Extensions/Carbon/ORTUSSelectionNoneTableViewCell.swift b/ORTUS/Extensions/Carbon/ORTUSSelectionNoneTableViewCell.swift new file mode 100644 index 0000000..636e920 --- /dev/null +++ b/ORTUS/Extensions/Carbon/ORTUSSelectionNoneTableViewCell.swift @@ -0,0 +1,22 @@ +// +// ORTUSSelectionNoneTableViewCell.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Carbon + +class ORTUSSelectionNoneTableViewCell: UITableViewCell, ComponentRenderable { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + selectionStyle = .none + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ORTUS/Extensions/Carbon/ORTUSTableViewAdapter.swift b/ORTUS/Extensions/Carbon/ORTUSTableViewAdapter.swift index 189f55b..884ca2b 100644 --- a/ORTUS/Extensions/Carbon/ORTUSTableViewAdapter.swift +++ b/ORTUS/Extensions/Carbon/ORTUSTableViewAdapter.swift @@ -9,12 +9,65 @@ import UIKit import Carbon +protocol ORTUSTableViewAdapterDelegate: AnyObject { + @available(iOS 13.0, *) + func contextMenuConfiguration( + forRowAt indexPath: IndexPath, + point: CGPoint + ) -> UIContextMenuConfiguration? + + @available(iOS 13.0, *) + func willPerformPreviewActionForMenu( + configuration: UIContextMenuConfiguration, + animator: UIContextMenuInteractionCommitAnimating + ) +} + +//extension ORTUSTableViewAdapterDelegate { +// @available(iOS 13.0, *) +// func contextMenuConfiguration( +// forRowAt indexPath: IndexPath, +// point: CGPoint +// ) -> UIContextMenuConfiguration? { +// return nil +// } +//} + class ORTUSTableViewAdapter: UITableViewAdapter { + weak var delegate: ORTUSTableViewAdapterDelegate? + + let selectionStyle: UITableViewCell.SelectionStyle + + init( + delegate: ORTUSTableViewAdapterDelegate? = nil, + selectionStyle: UITableViewCell.SelectionStyle = .default + ) { + self.delegate = delegate + self.selectionStyle = selectionStyle + + super.init() + } + override func cellRegistration(tableView: UITableView, indexPath: IndexPath, node: CellNode) -> CellRegistration { - CellRegistration(class: ORTUSTableViewCell.self) + switch selectionStyle { + case .none: + return CellRegistration(class: ORTUSSelectionNoneTableViewCell.self) + default: + return CellRegistration(class: ORTUSTableViewCell.self) + } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { super.tableView(tableView, cellForRowAt: indexPath) } + + @available(iOS 13.0, *) + func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + delegate?.contextMenuConfiguration(forRowAt: indexPath, point: point) + } + + @available(iOS 13.0, *) + func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + delegate?.willPerformPreviewActionForMenu(configuration: configuration, animator: animator) + } } diff --git a/ORTUS/Extensions/String.swift b/ORTUS/Extensions/String.swift index b5914bc..fb0e82a 100644 --- a/ORTUS/Extensions/String.swift +++ b/ORTUS/Extensions/String.swift @@ -9,6 +9,29 @@ import Foundation extension String { + // Source https://stackoverflow.com/a/47230632/7844080 + var htmlToAttributedString: NSAttributedString? { + guard let data = data(using: .utf8) else { + return nil + } + + do { + return try NSAttributedString( + data: data, + options: [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue + ], + documentAttributes: nil + ) + } catch { + return nil + } + } + var htmlToString: String { + return htmlToAttributedString?.string ?? "" + } + func generatePinAuthURL(withToken token: String) -> URL? { let string = "\(Global.Server.pinAuthURL)?module=PINAuth&locale=en&token=\(token)&goto=\(self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" diff --git a/ORTUS/Modules/Contacts/ContactsTableViewAdapter.swift b/ORTUS/Modules/Contacts/ContactsTableViewAdapter.swift index f715057..e849d6a 100644 --- a/ORTUS/Modules/Contacts/ContactsTableViewAdapter.swift +++ b/ORTUS/Modules/Contacts/ContactsTableViewAdapter.swift @@ -14,16 +14,16 @@ protocol ContactsTableViewAdapterDelegate: AnyObject { } class ContactsTableViewAdapter: ORTUSTableViewAdapter { - weak var delegate: ContactsTableViewAdapterDelegate? + weak var contactsDelegate: ContactsTableViewAdapterDelegate? init(delegate: ContactsTableViewAdapterDelegate?) { - self.delegate = delegate + self.contactsDelegate = delegate super.init() } func sectionIndexTitles(for tableView: UITableView) -> [String]? { - return delegate?.indexTitles() + return contactsDelegate?.indexTitles() } func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { diff --git a/ORTUS/Modules/Grades/GradesRouter.swift b/ORTUS/Modules/Grades/GradesRouter.swift index 9778da0..12aea0b 100644 --- a/ORTUS/Modules/Grades/GradesRouter.swift +++ b/ORTUS/Modules/Grades/GradesRouter.swift @@ -6,7 +6,11 @@ // Copyright (c) 2020 Firdavs. All rights reserved. // -final class GradesRouter: Router { - typealias Routes = Closable +final class GradesRouter: Router, MarkRoute { + typealias Routes = MarkRoute & Closable + + var transition: Transition { + PushTransition() + } } diff --git a/ORTUS/Modules/Grades/GradesViewController.swift b/ORTUS/Modules/Grades/GradesViewController.swift index fadddb1..322aa3b 100644 --- a/ORTUS/Modules/Grades/GradesViewController.swift +++ b/ORTUS/Modules/Grades/GradesViewController.swift @@ -9,20 +9,11 @@ import UIKit import Carbon import Promises +import Models -class GradesViewController: Module, ModuleViewModel { +class GradesViewController: ORTUSTableViewController, ModuleViewModel { var viewModel: GradesViewModel - weak var gradesView: GradesView! { return view as? GradesView } - weak var tableView: UITableView! { return gradesView.tableView } - - var refreshControl: UIRefreshControl! - - lazy var renderer = Renderer( - adapter: UITableViewAdapter(), - updater: UITableViewUpdater() - ) - init(viewModel: GradesViewModel) { self.viewModel = viewModel @@ -33,10 +24,6 @@ class GradesViewController: Module, ModuleViewModel { fatalError("init(coder:) has not been implemented") } - override func loadView() { - view = GradesView() - } - override func viewDidLoad() { super.viewDidLoad() @@ -44,12 +31,27 @@ class GradesViewController: Module, ModuleViewModel { EventLogger.log(.openedGrades) - prepareRefreshControl() - prepareData() - loadData() } + override func prepareData() { + super.prepareData() + + renderer.updater.isAnimationEnabledWhileScrolling = false + + navigationItem.title = L10n.Grades.title + + renderer.adapter.didSelect = { [unowned self] context in + guard let semesters = self.viewModel.studyPrograms.first?.semesters else { + return + } + + let mark = semesters[context.indexPath.section].marks[context.indexPath.row] + + self.openMark(mark) + } + } + func render() { refreshControl.endRefreshing() @@ -64,7 +66,7 @@ class GradesViewController: Module, ModuleViewModel { header: Header(title: semester.fullName.uppercased()) ) { Group(of: semester.marks) { mark in - GradeComponent(id: mark.id, mark: mark) + GradeComponent(mark: mark).identified(by: mark.id) } } } @@ -89,21 +91,11 @@ class GradesViewController: Module, ModuleViewModel { } } - @objc func refresh() { + override func refresh() { loadData(forceUpdate: true) } -} - -extension GradesViewController { - func prepareRefreshControl() { - refreshControl = UIRefreshControl() - tableView.refreshControl = refreshControl - refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged) - } - func prepareData() { - renderer.target = tableView - - navigationItem.title = L10n.Grades.title + func openMark(_ mark: Mark) { + viewModel.router.openMark(mark) } } diff --git a/ORTUS/Modules/Grades/Mark/MarkModuleBuilder.swift b/ORTUS/Modules/Grades/Mark/MarkModuleBuilder.swift new file mode 100644 index 0000000..78699ae --- /dev/null +++ b/ORTUS/Modules/Grades/Mark/MarkModuleBuilder.swift @@ -0,0 +1,24 @@ +// +// MarkModuleBuilder.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright (c) 2020 Firdavs. All rights reserved. +// + +import Models + +class MarkModuleBuilder: ModuleBuilder { + typealias M = MarkViewController + typealias P = Mark + + static func build(with parameter: Mark, customTransition transition: Transition? = nil) -> MarkViewController { + let router = MarkRouter() + let viewModel = MarkViewModel(mark: parameter, router: router) + let viewController = MarkViewController(viewModel: viewModel) + router.viewController = viewController + router.openTransition = transition + + return viewController + } +} diff --git a/ORTUS/Modules/Grades/Mark/MarkRouter.swift b/ORTUS/Modules/Grades/Mark/MarkRouter.swift new file mode 100644 index 0000000..d963499 --- /dev/null +++ b/ORTUS/Modules/Grades/Mark/MarkRouter.swift @@ -0,0 +1,12 @@ +// +// MarkRouter.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright (c) 2020 Firdavs. All rights reserved. +// + +final class MarkRouter: Router { + typealias Routes = Closable +} + diff --git a/ORTUS/Modules/Grades/GradesView.swift b/ORTUS/Modules/Grades/Mark/MarkView.swift similarity index 91% rename from ORTUS/Modules/Grades/GradesView.swift rename to ORTUS/Modules/Grades/Mark/MarkView.swift index 17a434b..3a16105 100644 --- a/ORTUS/Modules/Grades/GradesView.swift +++ b/ORTUS/Modules/Grades/Mark/MarkView.swift @@ -1,14 +1,14 @@ // -// GradesView.swift +// MarkView.swift // ORTUS // -// Created by Firdavs Khaydarov on 04/02/20. +// Created by Firdavs Khaydarov on 8/19/20. // Copyright (c) 2020 Firdavs. All rights reserved. // import UIKit -class GradesView: UIView { +class MarkView: UIView { let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.backgroundView = nil diff --git a/ORTUS/Modules/Grades/Mark/MarkViewController.swift b/ORTUS/Modules/Grades/Mark/MarkViewController.swift new file mode 100644 index 0000000..1c2b7a5 --- /dev/null +++ b/ORTUS/Modules/Grades/Mark/MarkViewController.swift @@ -0,0 +1,110 @@ +// +// MarkViewController.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright (c) 2020 Firdavs. All rights reserved. +// + +import UIKit +import Carbon + +class MarkViewController: Module, ModuleViewModel { + enum ID { + case cp, lector, type, date + } + + var viewModel: MarkViewModel + + weak var markView: MarkView! { return view as? MarkView } + weak var tableView: UITableView! { return markView.tableView } + + lazy var renderer = Renderer( + adapter: ORTUSTableViewAdapter(selectionStyle: .none), + updater: UITableViewUpdater() + ) + + init(viewModel: MarkViewModel) { + self.viewModel = viewModel + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = MarkView() + } + + override func viewDidLoad() { + super.viewDidLoad() + + prepareNavigationItem() + prepareData() + + render() + } + + func render() { + renderer.render { + Section( + id: "mark", + header: Header(title: "") + ) { + MarkComponent(mark: viewModel.mark).identified(by: viewModel.mark.id) + } + + Section( + id: "other", + header: Header(title: "") + ) { + TextDescriptionComponent( + text: "CP", + description: viewModel.mark.creditPoints + ).identified(by: ID.cp) + + TextDescriptionComponent( + text: "Lector", + description: viewModel.mark.lecturerFullName + ).identified(by: ID.lector) + + TextDescriptionComponent( + text: "Type", + description: viewModel.mark.markType + ).identified(by: ID.type) + + TextDescriptionComponent( + text: "Date", + description: formatDate(viewModel.mark.date) + ).identified(by: ID.date) + } + } + } + + func formatDate(_ date: String) -> String { + let dateFormatter = LatviaDateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.S" + + guard let parsedDate = dateFormatter.date(from: date) else { + return date + } + + dateFormatter.dateStyle = .long + dateFormatter.doesRelativeDateFormatting = true + + return dateFormatter.string(from: parsedDate) + } +} + +extension MarkViewController { + func prepareNavigationItem() { + navigationItem.largeTitleDisplayMode = .never + navigationItem.title = "Details" + } + + func prepareData() { + renderer.target = tableView + } +} diff --git a/ORTUS/Modules/Grades/Mark/MarkViewModel.swift b/ORTUS/Modules/Grades/Mark/MarkViewModel.swift new file mode 100644 index 0000000..5ffbc7e --- /dev/null +++ b/ORTUS/Modules/Grades/Mark/MarkViewModel.swift @@ -0,0 +1,20 @@ +// +// MarkViewModel.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright (c) 2020 Firdavs. All rights reserved. +// + +import Models + +class MarkViewModel: ViewModel { + let mark: Mark + + let router: MarkRouter.Routes + + init(mark: Mark, router: MarkRouter.Routes) { + self.mark = mark + self.router = router + } +} diff --git a/ORTUS/Modules/Home/HomeViewController.swift b/ORTUS/Modules/Home/HomeViewController.swift index 8a57f1f..b10025a 100644 --- a/ORTUS/Modules/Home/HomeViewController.swift +++ b/ORTUS/Modules/Home/HomeViewController.swift @@ -18,6 +18,8 @@ class HomeViewController: ORTUSTableViewController, ModuleViewModel { var viewModel: HomeViewModel + var previewingSemesterIndex: Int? + init(viewModel: HomeViewModel) { self.viewModel = viewModel @@ -104,8 +106,8 @@ class HomeViewController: ORTUSTableViewController, ModuleViewModel { Section(id: "courses", header: Header(title: "Courses".uppercased()), cells: { Group(of: viewModel.semesters.enumerated()) { (index, semester) in - TextComponent( - text: semester.name ?? "Other" + SemesterComponent( + semester: semester ).identified(by: index) } }) @@ -134,6 +136,36 @@ class HomeViewController: ORTUSTableViewController, ModuleViewModel { loadData() } + @available(iOS 13.0, *) + override func contextMenuConfiguration(forRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + guard indexPath.section == 1 else { + return nil + } + + previewingSemesterIndex = indexPath.row + + return UIContextMenuConfiguration( + identifier: nil, + previewProvider: { [unowned self] in + SemesterModuleBuilder.build(with: self.viewModel.semesters[indexPath.row]) + }, + actionProvider: nil + ) + } + + @available(iOS 13.0, *) + override func willPerformPreviewActionForMenu(configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + animator.addCompletion { + guard let previewingSemesterIndex = self.previewingSemesterIndex else { + return + } + + self.previewingSemesterIndex = nil + + self.viewModel.router.openSemester(self.viewModel.semesters[previewingSemesterIndex]) + } + } + @objc func scrollToTop() { guard let tabBarController = navigationController?.tabBarController, tabBarController.selectedIndex == Global.UI.TabBar.home.rawValue, diff --git a/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionView.swift b/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionView.swift new file mode 100644 index 0000000..2c851da --- /dev/null +++ b/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionView.swift @@ -0,0 +1,43 @@ +// +// EventDescriptionView.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/27/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit + +class EventDescriptionView: UIView { + let eventView = EventComponentView() + + let descriptionLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 16) + label.textColor = ColorCompatibility.secondaryLabel + label.numberOfLines = 0 + + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(eventView) + eventView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.left.right.equalToSuperview() + } + + addSubview(descriptionLabel) + descriptionLabel.snp.makeConstraints { + $0.top.equalTo(eventView.snp.bottom).offset(5) + $0.left.right.equalToSuperview().offset(Global.UI.edgeInset).inset(Global.UI.edgeInset) + $0.bottom.equalToSuperview().inset(5).priority(250) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionViewController.swift b/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionViewController.swift new file mode 100644 index 0000000..7a758af --- /dev/null +++ b/ORTUS/Modules/Schedule/Schedule Description/EventDescriptionViewController.swift @@ -0,0 +1,64 @@ +// +// EventDescriptionViewController.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/27/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Models + +class EventDescriptionViewController: UIViewController { + let event: Event + + weak var eventDescriptionView: EventDescriptionView! { return view as? EventDescriptionView } + weak var eventView: EventComponentView! { return eventDescriptionView.eventView } + weak var descriptionLabel: UILabel! { return eventDescriptionView.descriptionLabel } + + init(event: Event) { + self.event = event + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = EventDescriptionView() + } + + override func viewDidLoad() { + super.viewDidLoad() + + eventView.titleLabel.text = event.title + if event.allDayEvent { + eventView.timeLabel.text = "all-day" + } else { + eventView.timeLabel.text = event.timeParsed + } + + let description = event.description.htmlToString + descriptionLabel.text = description + + let eventTitleSize = (event.title as NSString) + .size(withAttributes: [.font: eventView.titleLabel.font]) + let eventHeight = 7 + (eventTitleSize.height < 44 ? 44 : eventTitleSize.height) + 7 + + let descriptionSize = (description as NSString) + .boundingRect( + with: CGSize( + width: UIScreen.main.bounds.width - Global.UI.edgeInset * 2, + height: .greatestFiniteMagnitude), + options: [.usesFontLeading, .usesLineFragmentOrigin], + attributes: [.font: descriptionLabel.font], + context: nil + ) + + preferredContentSize = CGSize( + width: UIScreen.main.bounds.width, + height: eventHeight + 5 + descriptionSize.height) + } +} diff --git a/ORTUS/Modules/Schedule/ScheduleRouter.swift b/ORTUS/Modules/Schedule/ScheduleRouter.swift index 4be76c6..c66780b 100644 --- a/ORTUS/Modules/Schedule/ScheduleRouter.swift +++ b/ORTUS/Modules/Schedule/ScheduleRouter.swift @@ -6,6 +6,10 @@ // Copyright (c) 2019 Firdavs. All rights reserved. // -final class ScheduleRouter: Router { - typealias Routes = Closable +final class ScheduleRouter: Router, BrowserRoute { + typealias Routes = BrowserRoute & Closable + + var transition: Transition { + PushTransition() + } } diff --git a/ORTUS/Modules/Schedule/ScheduleTableViewAdapter.swift b/ORTUS/Modules/Schedule/ScheduleTableViewAdapter.swift index 3eee533..ff58020 100644 --- a/ORTUS/Modules/Schedule/ScheduleTableViewAdapter.swift +++ b/ORTUS/Modules/Schedule/ScheduleTableViewAdapter.swift @@ -8,31 +8,69 @@ import UIKit import Carbon +import Models + +protocol ScheduleTableViewAdapterDataSource: class { + func item(for indexPath: IndexPath) -> ScheduleItem? +} protocol ScheduleTableViewAdapterDelegate: class { - + func openLink(_ url: URL) } class ScheduleTableViewAdapter: UITableViewAdapter { + weak var dataSource: ScheduleTableViewAdapterDataSource? + weak var delegate: ScheduleTableViewAdapterDelegate? - init(delegate: ScheduleTableViewAdapterDelegate? = nil) { + init( + dataSource: ScheduleTableViewAdapterDataSource? = nil, + delegate: ScheduleTableViewAdapterDelegate? = nil + ) { + self.dataSource = dataSource self.delegate = delegate super.init() } -// @available(iOS 13.0, *) -// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -// return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in -// let open = UIAction( -// title: "Open Link", -// image: UIImage(systemName: "link") -// ) { [unowned self] action in -// print("Link") -// } -// -// return UIMenu(title: "", children: [open]) -// }) -// } + @available(iOS 13.0, *) + func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + guard let scheduleItem = dataSource?.item(for: indexPath) else { + return nil + } + + var previewProvider: UIContextMenuContentPreviewProvider? + var actionProdivder: UIContextMenuActionProvider? + + if let event = scheduleItem.item(as: Event.self) { + previewProvider = { + return EventDescriptionViewController(event: event) + } + + actionProdivder = { _ in + let open = UIAction( + title: "Open Link", + image: UIImage(systemName: "link") + ) { [weak self] action in + guard let url = URL(string: event.link) else { + return + } + + self?.delegate?.openLink(url) + } + + return UIMenu(title: "", children: [open]) + } + } + + if let lecture = scheduleItem.item(as: Lecture.self) { + return nil + } + + return UIContextMenuConfiguration( + identifier: nil, + previewProvider: previewProvider, + actionProvider: actionProdivder + ) + } } diff --git a/ORTUS/Modules/Schedule/ScheduleViewController.swift b/ORTUS/Modules/Schedule/ScheduleViewController.swift index 008ae1f..63ca802 100644 --- a/ORTUS/Modules/Schedule/ScheduleViewController.swift +++ b/ORTUS/Modules/Schedule/ScheduleViewController.swift @@ -35,9 +35,10 @@ class ScheduleViewController: Module, ModuleViewModel { return bar }() + lazy var adapter = ScheduleTableViewAdapter(dataSource: self, delegate: self) lazy var renderer = Renderer( - adapter: ScheduleTableViewAdapter(), + adapter: self.adapter, updater: UITableViewUpdater() ) @@ -250,6 +251,26 @@ extension ScheduleViewController { } } +extension ScheduleViewController: ScheduleTableViewAdapterDataSource, ScheduleTableViewAdapterDelegate { + func item(for indexPath: IndexPath) -> ScheduleItem? { + let cell = renderer.data[indexPath.section].cells[indexPath.row] + + if let event = cell.component(as: EventComponent.self)?.event { + return ScheduleItem(event, time: event.time) + } + + if let lecture = cell.component(as: LectureComponent.self)?.lecture { + return ScheduleItem(lecture, time: lecture.timeFrom) + } + + return nil + } + + func openLink(_ url: URL) { + viewModel.router.openBrowser(url.absoluteString) + } +} + extension ScheduleViewController: UIToolbarDelegate { func position(for bar: UIBarPositioning) -> UIBarPosition { return .top diff --git a/ORTUS/Modules/Schedule/ScheduleViewModel.swift b/ORTUS/Modules/Schedule/ScheduleViewModel.swift index 93a7b61..8515359 100644 --- a/ORTUS/Modules/Schedule/ScheduleViewModel.swift +++ b/ORTUS/Modules/Schedule/ScheduleViewModel.swift @@ -106,10 +106,10 @@ class ScheduleViewModel: ViewModel { for pair in sortedResponse { var items: [ScheduleItem] = [] if sharedUserDefaults?.value(for: .showEvents) == true { - pair.value.events.forEach { items.append(ScheduleItem($0, time: $0.time)) } + pair.value.events.forEach { items.append(ScheduleItem($0, time: $0.timeParsed)) } } - pair.value.lectures.forEach { items.append(ScheduleItem($0, time: $0.timeFrom)) } + pair.value.lectures.forEach { items.append(ScheduleItem($0, time: $0.timeFromParsed)) } items.sort(by: { guard let firstDate = $0.timeDate, let secondDate = $1.timeDate else { return false diff --git a/ORTUS/Modules/Settings/SettingsViewController.swift b/ORTUS/Modules/Settings/SettingsViewController.swift index 5b01e0e..9614c90 100644 --- a/ORTUS/Modules/Settings/SettingsViewController.swift +++ b/ORTUS/Modules/Settings/SettingsViewController.swift @@ -229,14 +229,6 @@ class SettingsViewController: ORTUSTableViewController, ModuleViewModel, AlertPr viewModel.router.openLogin() } - - func deselectSelectedRow() { - guard let selectedIndexPath = tableView.indexPathForSelectedRow else { - return - } - - tableView.deselectRow(at: selectedIndexPath, animated: true) - } } extension SettingsViewController: MFMailComposeViewControllerDelegate { diff --git a/ORTUS/Routing/Routes/Grades/MarkRoute.swift b/ORTUS/Routing/Routes/Grades/MarkRoute.swift new file mode 100644 index 0000000..55f24cf --- /dev/null +++ b/ORTUS/Routing/Routes/Grades/MarkRoute.swift @@ -0,0 +1,23 @@ +// +// MarkRoute.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Models + +protocol MarkRoute: Route { + func openMark(_ mark: Mark) +} + +extension MarkRoute where Self: RouterProtocol { + func openMark(_ mark: Mark) { + let transition = self.transition + let module = MarkModuleBuilder.build(with: mark, customTransition: transition) + + open(module, transition: transition, completion: nil) + } +} diff --git a/ORTUS/Views/Components/Common/TextDescriptionComponent.swift b/ORTUS/Views/Components/Common/TextDescriptionComponent.swift new file mode 100644 index 0000000..a4a0de2 --- /dev/null +++ b/ORTUS/Views/Components/Common/TextDescriptionComponent.swift @@ -0,0 +1,28 @@ +// +// TextDescriptionComponent.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Carbon + +struct TextDescriptionComponent: Component { + var text: String + var description: String? + + func renderContent() -> TextDescriptionComponentView { + return TextDescriptionComponentView() + } + + func render(in content: TextDescriptionComponentView) { + content.textLabel.text = text + content.descriptionLabel.text = description + } + + func referenceSize(in bounds: CGRect) -> CGSize? { + return CGSize(width: bounds.width, height: 60) + } +} diff --git a/ORTUS/Views/Components/Common/TextDescriptionComponentView.swift b/ORTUS/Views/Components/Common/TextDescriptionComponentView.swift new file mode 100644 index 0000000..64cf1d6 --- /dev/null +++ b/ORTUS/Views/Components/Common/TextDescriptionComponentView.swift @@ -0,0 +1,42 @@ +// +// TextDescriptionComponentView.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit + +class TextDescriptionComponentView: UIView { + let textLabel = UILabel() + + let descriptionLabel: UILabel = { + let label = UILabel() + label.textColor = ColorCompatibility.secondaryLabel + + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(textLabel) + addSubview(descriptionLabel) + + textLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.left.equalToSuperview().offset(Global.UI.edgeInset) + $0.right.equalTo(descriptionLabel.snp.left).offset(-10).priority(250) + } + + descriptionLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.right.equalToSuperview().inset(Global.UI.edgeInset) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ORTUS/Views/Components/Course/SemesterComponent.swift b/ORTUS/Views/Components/Course/SemesterComponent.swift new file mode 100644 index 0000000..aea6fc8 --- /dev/null +++ b/ORTUS/Views/Components/Course/SemesterComponent.swift @@ -0,0 +1,28 @@ +// +// SemesterComponent.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/20/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Carbon +import Models + +struct SemesterComponent: Component { + var semester: Semester + + func renderContent() -> SemesterComponentView { + return SemesterComponentView() + } + + func render(in content: SemesterComponentView) { + content.coursesCountLabel.text = "\(semester.courses.count) courses" + content.textLabel.text = semester.name ?? "Other" + } + + func referenceSize(in bounds: CGRect) -> CGSize? { + return CGSize(width: bounds.width, height: 60) + } +} diff --git a/ORTUS/Views/Components/Course/SemesterComponentView.swift b/ORTUS/Views/Components/Course/SemesterComponentView.swift new file mode 100644 index 0000000..4ce89ed --- /dev/null +++ b/ORTUS/Views/Components/Course/SemesterComponentView.swift @@ -0,0 +1,67 @@ +// +// SemesterComponentView.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/20/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit + +class SemesterComponentView: UIView { + let coursesCountLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.textColor = ColorCompatibility.secondaryLabel + + return label + }() + + let textLabel = UILabel() + + let contentLayoutGuide = UILayoutGuide() + + let rightAccessoryView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "chevronRight") + imageView.contentMode = .scaleAspectFit + imageView.tintColor = ColorCompatibility.tertiaryLabel + + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(rightAccessoryView) + rightAccessoryView.snp.makeConstraints { + $0.size.equalTo(18) + $0.centerY.equalToSuperview() + $0.right.equalToSuperview().inset(Global.UI.edgeInset) + } + + addLayoutGuide(contentLayoutGuide) + contentLayoutGuide.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.left.equalToSuperview().offset(Global.UI.edgeInset) + $0.right.equalTo(rightAccessoryView.snp.left).offset(10) + } + + addSubview(textLabel) + textLabel.snp.makeConstraints { + $0.top.equalTo(contentLayoutGuide) + $0.left.right.equalTo(contentLayoutGuide) + } + + addSubview(coursesCountLabel) + coursesCountLabel.snp.makeConstraints { + $0.top.equalTo(textLabel.snp.bottom).offset(2) + $0.left.right.equalTo(contentLayoutGuide) + $0.bottom.equalTo(contentLayoutGuide) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ORTUS/Views/Components/Grades/GradeComponent.swift b/ORTUS/Views/Components/Grades/GradeComponent.swift index de42b80..fc12caf 100644 --- a/ORTUS/Views/Components/Grades/GradeComponent.swift +++ b/ORTUS/Views/Components/Grades/GradeComponent.swift @@ -10,8 +10,7 @@ import UIKit import Carbon import Models -struct GradeComponent: IdentifiableComponent { - var id: Int +struct GradeComponent: Component { var mark: Mark func renderContent() -> GradeComponentView { @@ -20,16 +19,11 @@ struct GradeComponent: IdentifiableComponent { func render(in content: GradeComponentView) { content.courseLabel.text = mark.courseFullName - content.creditPointsLabel.text = "\(mark.creditPoints) CP" content.lecturerLabel.text = mark.lecturerFullName content.gradeLabel.text = mark.mark ?? "0" } func referenceSize(in bounds: CGRect) -> CGSize? { - return nil - } - - func shouldContentUpdate(with next: GradeComponent) -> Bool { - return id != next.id + return CGSize(width: bounds.width, height: 67) } } diff --git a/ORTUS/Views/Components/Grades/GradeComponentView.swift b/ORTUS/Views/Components/Grades/GradeComponentView.swift index fe5f167..e35031b 100644 --- a/ORTUS/Views/Components/Grades/GradeComponentView.swift +++ b/ORTUS/Views/Components/Grades/GradeComponentView.swift @@ -9,9 +9,11 @@ import UIKit class GradeComponentView: UIView { - let gradeContainerView: CircleView = { - let view = CircleView() + let gradeContainerView: UIView = { + let view = UIView() view.backgroundColor = Asset.Colors.tintColor.color + view.layer.cornerRadius = 12 + view.clipsToBounds = true return view }() @@ -24,19 +26,7 @@ class GradeComponentView: UIView { return label }() - let courseLabel: UILabel = { - let label = UILabel() - label.numberOfLines = 2 - - return label - }() - - let creditPointsLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 15) - - return label - }() + let courseLabel = UILabel() let lecturerLabel: UILabel = { let label = UILabel() @@ -46,14 +36,21 @@ class GradeComponentView: UIView { return label }() + let rightAccessoryView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "chevronRight") + imageView.contentMode = .scaleAspectFit + imageView.tintColor = ColorCompatibility.tertiaryLabel + + return imageView + }() + override init(frame: CGRect) { super.init(frame: frame) - - backgroundColor = ColorCompatibility.secondarySystemGroupedBackground - + + prepareRightAccessory() prepareGradeLabel() prepareCourseLabel() - prepareCreditPointsLabel() prepareLecturerLabel() } @@ -63,12 +60,21 @@ class GradeComponentView: UIView { } extension GradeComponentView { + func prepareRightAccessory() { + addSubview(rightAccessoryView) + rightAccessoryView.snp.makeConstraints { + $0.size.equalTo(18) + $0.centerY.equalToSuperview() + $0.right.equalToSuperview().inset(Global.UI.edgeInset) + } + } + func prepareGradeLabel() { addSubview(gradeContainerView) gradeContainerView.snp.makeConstraints { - $0.size.equalTo(50) - $0.top.equalToSuperview().offset(10) - $0.right.equalToSuperview().inset(Global.UI.edgeInset) + $0.top.bottom.equalToSuperview().offset(10).inset(10) + $0.width.equalTo(gradeContainerView.snp.height) + $0.left.equalToSuperview().offset(Global.UI.edgeInset) } gradeContainerView.addSubview(gradeLabel) @@ -81,27 +87,17 @@ extension GradeComponentView { addSubview(courseLabel) courseLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(10) - $0.left.equalToSuperview().offset(Global.UI.edgeInset) - $0.right.equalTo(gradeContainerView.snp.left).offset(-15) - } - } - - func prepareCreditPointsLabel() { - addSubview(creditPointsLabel) - creditPointsLabel.snp.makeConstraints { - $0.top.equalTo(courseLabel.snp.bottom).offset(5) - $0.left.equalToSuperview().offset(Global.UI.edgeInset) - $0.right.equalTo(gradeContainerView.snp.left).offset(-15) + $0.left.equalTo(gradeContainerView.snp.right).offset(10) + $0.right.equalTo(rightAccessoryView.snp.left).offset(-10) } } func prepareLecturerLabel() { addSubview(lecturerLabel) lecturerLabel.snp.makeConstraints { - $0.top.equalTo(creditPointsLabel.snp.bottom).offset(10) - $0.left.equalToSuperview().offset(Global.UI.edgeInset) - $0.right.equalTo(gradeContainerView.snp.left).offset(-15) - $0.bottom.equalToSuperview().inset(10).priority(250) + $0.left.equalTo(gradeContainerView.snp.right).offset(10) + $0.right.equalTo(rightAccessoryView.snp.left).offset(-10) + $0.bottom.equalToSuperview().inset(10) } } } diff --git a/ORTUS/Views/Components/Grades/MarkComponent.swift b/ORTUS/Views/Components/Grades/MarkComponent.swift new file mode 100644 index 0000000..8b4474e --- /dev/null +++ b/ORTUS/Views/Components/Grades/MarkComponent.swift @@ -0,0 +1,28 @@ +// +// MarkComponent.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit +import Carbon +import Models + +struct MarkComponent: Component { + var mark: Mark + + func renderContent() -> MarkComponentView { + return MarkComponentView() + } + + func render(in content: MarkComponentView) { + content.courseLabel.text = mark.courseFullName + content.gradeLabel.text = mark.mark ?? "0" + } + + func referenceSize(in bounds: CGRect) -> CGSize? { + return nil + } +} diff --git a/ORTUS/Views/Components/Grades/MarkComponentView.swift b/ORTUS/Views/Components/Grades/MarkComponentView.swift new file mode 100644 index 0000000..5282e89 --- /dev/null +++ b/ORTUS/Views/Components/Grades/MarkComponentView.swift @@ -0,0 +1,66 @@ +// +// MarkComponentView.swift +// ORTUS +// +// Created by Firdavs Khaydarov on 8/19/20. +// Copyright © 2020 Firdavs. All rights reserved. +// + +import UIKit + +class MarkComponentView: UIView { + let gradeContainerView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Colors.tintColor.color + view.layer.cornerRadius = 16 + view.clipsToBounds = true + + return view + }() + + let gradeLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 34, weight: .medium) + label.textColor = .white + + return label + }() + + let courseLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 22, weight: .medium) + label.numberOfLines = 0 + + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(courseLabel) + addSubview(gradeContainerView) + gradeContainerView.addSubview(gradeLabel) + + courseLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(15) + $0.left.equalToSuperview().offset(Global.UI.edgeInset) + $0.right.equalTo(gradeContainerView.snp.left).offset(-15) + $0.bottom.equalToSuperview().inset(15).priority(250) + } + + gradeContainerView.snp.makeConstraints { + $0.size.equalTo(70) + $0.centerY.equalToSuperview() + $0.top.bottom.equalToSuperview().offset(15).inset(15).priority(500) + $0.right.equalToSuperview().inset(Global.UI.edgeInset) + } + + gradeLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ORTUS/Views/Components/Schedule/EventComponent.swift b/ORTUS/Views/Components/Schedule/EventComponent.swift index cd51ec8..0626393 100644 --- a/ORTUS/Views/Components/Schedule/EventComponent.swift +++ b/ORTUS/Views/Components/Schedule/EventComponent.swift @@ -24,7 +24,7 @@ struct EventComponent: IdentifiableComponent { if event.allDayEvent { content.timeLabel.text = "all-day" } else { - content.timeLabel.text = event.time + content.timeLabel.text = event.timeParsed } } diff --git a/ORTUS/Views/View Controllers/ORTUSTableViewController.swift b/ORTUS/Views/View Controllers/ORTUSTableViewController.swift index 01bbe09..43608bb 100644 --- a/ORTUS/Views/View Controllers/ORTUSTableViewController.swift +++ b/ORTUS/Views/View Controllers/ORTUSTableViewController.swift @@ -10,14 +10,14 @@ import UIKit import Carbon import Promises -class ORTUSTableViewController: Module { +class ORTUSTableViewController: Module, ORTUSTableViewAdapterDelegate { weak var baseView: ORTUSTableView! { return view as? ORTUSTableView } weak var tableView: UITableView! { return baseView.tableView } var refreshControl: UIRefreshControl! lazy var renderer = Renderer( - adapter: ORTUSTableViewAdapter(), + adapter: ORTUSTableViewAdapter(delegate: self), updater: UITableViewUpdater() ) @@ -64,4 +64,24 @@ class ORTUSTableViewController: Module { func prepareData() { renderer.target = tableView } + + func deselectSelectedRow() { + guard let selectedIndexPath = tableView.indexPathForSelectedRow else { + return + } + + tableView.deselectRow(at: selectedIndexPath, animated: true) + } + + // MARK: - ORTUSTableViewAdapterDelegate + + @available(iOS 13.0, *) + func contextMenuConfiguration(forRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + return nil + } + + @available(iOS 13.0, *) + func willPerformPreviewActionForMenu(configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + + } } diff --git a/Schedule/Components/EventComponent.swift b/Schedule/Components/EventComponent.swift index 2e91f20..83a694d 100644 --- a/Schedule/Components/EventComponent.swift +++ b/Schedule/Components/EventComponent.swift @@ -27,13 +27,14 @@ struct EventComponent: IdentifiableComponent { if event.allDayEvent { time += "all-day" } else { - time += event.time + time += event.timeParsed } if let date = date { time += ", " let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_LV") dateFormatter.timeZone = TimeZone(identifier: "Europe/Riga") dateFormatter.dateFormat = "EEEE, d MMMM" diff --git a/Schedule/TodayViewController.swift b/Schedule/TodayViewController.swift index e3e5c36..9411807 100644 --- a/Schedule/TodayViewController.swift +++ b/Schedule/TodayViewController.swift @@ -60,7 +60,7 @@ class TodayViewController: UIViewController { let itemsDate = items.key.components(separatedBy: "-") .compactMap { Int($0) } // Date from time of event or date from string - var eventDate = item.timeDate ?? dateFormatter.date(from: items.key) + var eventDate = event.date ?? item.timeDate ?? dateFormatter.date(from: items.key) // Add year, month and day to only time date if eventDate != nil, itemsDate.count == 3 { @@ -132,6 +132,7 @@ class TodayViewController: UIViewController { private func sortSchedule(from response: ScheduleResponse) -> Schedule { let sortedResponse = response.result.sorted(by: { let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_LV") dateFormatter.timeZone = TimeZone(identifier: "Europe/Riga") dateFormatter.dateFormat = "yyyy-MM-dd" @@ -148,7 +149,7 @@ class TodayViewController: UIViewController { var items: [ScheduleItem] = [] if UserDefaults(suiteName: "group.me.recouse.ORTUS")?.bool(forKey: "show_events") == true { - pair.value.events.forEach { items.append(ScheduleItem($0, time: $0.time)) } + pair.value.events.forEach { items.append(ScheduleItem($0, time: $0.timeParsed)) } } pair.value.lectures.forEach { items.append(ScheduleItem($0, time: $0.timeFromParsed)) }